【权限管理】使用cookie,session实现权限管理
其他文章可以通过菜单查看:【BookCase 菜单】
是时候给小项目加上权限系统了,初步使用SSO(Single Sign On)单点登录。
什么是单点登录? 所谓单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录。
例如一开始我们有OA系统,有进销存系统,有CRM系统,各个系统都有自己的用户管理,登录起来太麻烦了,假如我们做到账户一次登录全部使用,SSO的作用就是这个。
我们一步步来,这里先通过session模拟登录功能并控制权限管理。
说到SSO,就不得不提Cookie和Session,当用户在浏览器访问数据的时候,就会产生会话Session。 会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。
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 可以设置过期时间 setMaxAge,单位s,默认-1不过期。
为了保证安全,可以设置只允许Http协议访问 cookie.setHttpOnly(true);
设置只允许Https协议访问 cookie.setSecure(true);
设置域名,必须以 . 开始, cookie.setDomain(“.baidu.com”);
设置作用路径,必须以/结尾, setPath(“/a/”)
还有一些其他设置等。Cookie无法被修改和删除,假如设置同样的Cookie会发送覆盖,覆盖内容是value和maxAge。 其他的设置必须相同。
Cookie允许被js等拦截解析,不安全。 如果浏览器禁用Cookie,那么Cookie将无法被使用。 Cookie可以保持文本文件和二进制文件,其他信息无法被保存。
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 可以执行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
@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 来控制权限系统是servlet最原生的权限管理,也是最基础的权限管理。
特点简单易用,功能齐全。
后续会继续结合RBAC权限模型修改session登录,并逐步替换成Spring Security + JWT 进行权限管理。
感谢观看。