【权限管理】使用cookie,session实现权限管理

【权限管理】使用cookie,session实现权限管理

其他文章可以通过菜单查看:【BookCase 菜单】

1、前言

是时候给小项目加上权限系统了,初步使用SSO(Single Sign On)单点登录。

什么是单点登录? 所谓单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录。

例如一开始我们有OA系统,有进销存系统,有CRM系统,各个系统都有自己的用户管理,登录起来太麻烦了,假如我们做到账户一次登录全部使用,SSO的作用就是这个。

我们一步步来,这里先通过session模拟登录功能并控制权限管理。

2、使用

说到SSO,就不得不提Cookie和Session,当用户在浏览器访问数据的时候,就会产生会话Session。 会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。

2.1、Cookie

Cookie 由于Http协议是无状态的,什么是无状态?就是当我们使用浏览器访问的时候,所有的Http请求都是相同的,并且无法感知到用户的信息。 这个时候就需要一种技术来识别是那个用户在操作,Cookie 就是来确认用户信息而产生的技术。

Cookie 协议是一种标准,在java中有相应的实现,具体在Servlet中。 示例:


@RestController
@RequestMapping(value = "/test")
public class TestController {

    @RequestMapping("/setCookie")
    public String setCookie(HttpServletRequest request, HttpServletResponse response) {
        Cookie cookie = new Cookie("username", "password");
        cookie.setMaxAge(10);
        cookie.setPath("/");
        // 只允许http 协议传输
        cookie.setHttpOnly(true);
        // 设置域名
//        cookie.setDomain("");
        // 只允许https
        cookie.setSecure(false);
        // 注释
        cookie.setComment("");
        // cookie 协议版本 默认 0 netspace 版本 1 RFC 2109 版本
        cookie.setVersion(0);
        response.addCookie(cookie);
        return "username";
    }

    @RequestMapping("/getCookie")
    public String getCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            System.out.println(cookie.getName());
            System.out.println(cookie.getValue());
            return "获取cookie成功,执行业务!";
        }
        return "获取cookie失败!重新获取";
    }
}

示例中只是模拟Cookie 的使用,没有使用最原始的Servlet写法。 通过先访问 /setCookie,再访问 /getCookie 如果浏览器没有禁用Cookie,可以看到控制台打印Cookie中的内容。

通过这种方式可以确认用户的登录,用户的访问等信息。

  • Cookie的特点

如上述代码所示:

Cookie 可以设置过期时间 setMaxAge,单位s,默认-1不过期。

为了保证安全,可以设置只允许Http协议访问 cookie.setHttpOnly(true);

设置只允许Https协议访问 cookie.setSecure(true);

设置域名,必须以 . 开始, cookie.setDomain(“.baidu.com”);

设置作用路径,必须以/结尾, setPath(“/a/”)

还有一些其他设置等。Cookie无法被修改和删除,假如设置同样的Cookie会发送覆盖,覆盖内容是value和maxAge。 其他的设置必须相同。

  • Cookie的缺点

Cookie允许被js等拦截解析,不安全。 如果浏览器禁用Cookie,那么Cookie将无法被使用。 Cookie可以保持文本文件和二进制文件,其他信息无法被保存。

2.2、Session

Session 是与Cookie协同工作产生。Cookie是用户信息存储在浏览器中,而Session的信息是存储在服务器端。

Session代表就是一次会话,当浏览器打开访问会话产生,浏览器关闭会话结束。 也就是说我们访问一个网站,假如在打开之后,进行购物,加入购物车,结账都是一次会话产生的内容。

示例:


@RestController
@RequestMapping(value = "/test")
public class TestController {
    @RequestMapping("/setSession")
    public String setSession(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession();
        session.setAttribute("username", "password");
        session.setMaxInactiveInterval(10);
// 获取创建时间,毫秒数
        long creationTime = session.getCreationTime();
        return session.getId();
    }

    @RequestMapping("/getSession")
    public String getSession(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession();
        Object username = session.getAttribute("username");
        // 获取创建时间,毫秒数
        long creationTime = session.getCreationTime();
        // 获取所有session keys
        Enumeration<String> attributeNames = session.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            System.out.println(attributeNames.nextElement());
        }
        // 是否新创建
        boolean aNew = session.isNew();
        // 立即过期
//        session.invalidate();
        return String.valueOf(username);
    }
}
  • Session的特点

session 可以执行set get放入session 内容,例如用户信息等。
用户在访问服务器时候创建session,但是只有访问jsp或者servlet才会有。并会生成一个唯一的id,可以通过getCreationTime获取创建时间,通过getId获取唯一id。

session 默认过期时间是30分钟。可以通过setMaxInactiveInterval设置过期时间,单位秒。
session 可以执行删除操作,也就是立即过期invalidate。由于可以设置过期时间,也就可以产生登录延时操作。

session 和 Cookie 是协同工作的,生成sesson 之后会返回给浏览器sessionId。

如果浏览器cookie 被禁用,那么sessionId如何进行传输?这个时候可以通过重定向进行传输sessionId。

Cookie 和 Session 参考博客:https://www.cnblogs.com/l199616j/p/11195667.html

2.3、模拟登录验证

@RestController
@Api(value = "用户登录", tags = "用户登录")
@RequestMapping("/login")
@Slf4j
public class LoginController {


    static Map<String, String> usersMap = new HashMap<>();

    static {
        usersMap.put("user1", "123456");
    }

    @GetMapping("")
    @ApiOperation(value = "用户登录", notes = "用户登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "username", value = "用户名"),
            @ApiImplicitParam(name = "password", value = "密码"),
    })
    public Result<String> login(HttpServletRequest request,
                                @RequestParam(name = "username",required = false) String username,
                                @RequestParam(name = "password",required = false) String password) {
        if(StringUtils.isEmpty(username)|| StringUtils.isEmpty(password)){
            return Result.fail("用户名或账号密码未填写,请正确输入");
        }
        // 判断登录
        if (!usersMap.containsKey(username) || !usersMap.get(username).equals(password)) {
            return Result.fail("用户名或账号密码不正确,请重新登录");
        }

        HttpSession session = request.getSession();
        session.setAttribute("loginUser", username);

        return Result.success("登录成功!");
    }

    @GetMapping("/logOut")
    @ApiOperation(value = "用户登出", notes = "用户登出")
    public Result<String> logOut(HttpServletRequest request) {
        request.getSession().invalidate();
        log.info("" + request.getSession().getMaxInactiveInterval());
        return Result.success("登出成功!");
    }

    @GetMapping("/islogin")
    @ApiOperation(value = "判断是否登录", notes = "判断是否登录")
    public Result<String> islogin(HttpServletRequest request) {

        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");

        if (Objects.isNull(loginUser)) {
            return Result.fail("账号未登录,请重新登录!");
        }

        return Result.success("账号已登录!");
    }
}

这里不能调用接口验证是否登录,添加拦截器,这里应该重定向到登录页面,没有页面,所以直接返回了验证结果未登录
示例:

@Component
@Slf4j
public class PermissionIntercept extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.debug("preHandle ...");

        HttpSession session = request.getSession();

        Object loginUser = session.getAttribute("loginUser");
        
        if (Objects.isNull(loginUser)) {
            // 重定向到登录页,这里没有设置前端,重定向到验证返回错误
            response.setStatus(302);
            response.setHeader("location", request.getContextPath()+"/login/islogin");
            return false;
        }
        
        return super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.debug("postHandle ...");
        super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
         log.debug("afterCompletion ...");
    }
}

并添加到拦截器排除登录URL:


@Component
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private PermissionIntercept permissionIntercept;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(permissionIntercept).excludePathPatterns("/login/**","/doc.html");
        WebMvcConfigurer.super.addInterceptors(registry);
    }
}

以上Result为统一返回结构对象:

@Data
@ApiModel(value = "统一响应消息报文")
public class Result<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "状态码", required = true)
    private int code;

    @ApiModelProperty(value = "消息内容", required = true)
    private String msg;

    @ApiModelProperty(value = "时间戳", required = true)
    private long time;

    @ApiModelProperty(value = "业务数据")

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private T data;

    private Result() {
        this.time = System.currentTimeMillis();
    }

    private Result(ResultCode resultCode) {
        this(resultCode, null, resultCode.getMessage());
    }

    private Result(ResultCode resultCode, String msg) {
        this(resultCode, null, msg);
    }

    private Result(ResultCode resultCode, T data) {
        this(resultCode, data, resultCode.getMessage());
    }

    private Result(ResultCode resultCode, T data, String msg) {
        this(resultCode.getCode(), data, msg);
    }

    private Result(int code, T data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
        this.time = System.currentTimeMillis();
    }

    public static <T> Result<T> success(String msg) {
        return new Result<>(ResultCode.SUCCESS, msg);
    }

    public static <T> Result<T> data(T data) {
        return data(ResultCode.SUCCESS.getCode(), data, ResultCode.SUCCESS.getMessage());
    }

    public static <T> Result<T> data(int code, T data, String msg) {
        return new Result<>(code, data, data == null ? "返回数据为空" : msg);
    }

    public static <T> Result<T> fail() {
        return new Result<>(ResultCode.FAILURE, ResultCode.FAILURE.getMessage());
    }

    public static <T> Result<T> fail(String msg) {
        return new Result<>(ResultCode.FAILURE, msg);
    }

    public static <T> Result<T> fail(int code, String msg) {
        return new Result<>(code, null, msg);
    }
}

错误码枚举对象:

@Getter
public enum ResultCode {
    /**
     * 操作成功
     */
    SUCCESS(200, "操作成功"),
    /**
     * 业务异常
     */
    FAILURE(400, "业务异常"),
    ;

    private int code;
    private String message;

    ResultCode(int code,String message){
        this.code = code;
        this.message = message;
    }
}
  • 测试

直接访问获取数据:

【权限管理】使用cookie,session实现权限管理_第1张图片

点击登录后测试获取数据:

【权限管理】使用cookie,session实现权限管理_第2张图片

3、总结

通过cookie 和 session 来控制权限系统是servlet最原生的权限管理,也是最基础的权限管理。

特点简单易用,功能齐全。

后续会继续结合RBAC权限模型修改session登录,并逐步替换成Spring Security + JWT 进行权限管理。

感谢观看。

你可能感兴趣的:(微服务,java,intercepter,spring,cookie/session,权限管理)