SpringBoot:Token登录、认证、授权

Session、Cookie、Token的区别

网上这部分的资料很多。比如可以参考https://www.cnblogs.com/moyand/p/9047978.html等等。对于一些概念简单归纳一下:

  1. 由于HTTP都是无状态请求,这些都是为了让服务端“记住”用户而发明出来的方法。为确保用户会话的独立性和安全性,不能使用明文保存用户信息(如userId),而应使用加密后的信息储存。
  2. Session即会话,存放在服务端,用于标识用户的一次登录访问的信息。当某一个用户第一次调用一个Servlet时,服务器会生成一个HttpSession的对象,里面使用sessionId来作为该用户的唯一标识码。
  3. Cookie为存储在客户端的“小型文本文件”,保存用户的登录信息。使用Cookie+Session的认证方法,一般是在用户登录后获得服务器返回的sessionId存放到浏览器的Cookie里,然后浏览器在会话访问期间把Cookie放在Http请求头里完成认证。
  4. Cookie+Session的认证方法有以下缺点:①用户的每次访问生成的Session都要存放在服务器中,增大了服务端开销,此外存放在一个服务器中的sessionId不属于其他服务器,不便于分布式系统的水平扩展。②Cookie容易被截获,服务端易遭受跨站伪造请求攻击(CSRF)。③Cookie适用于浏览器,而对于移动端App等客户端不太友好。
  5. Token也是由服务端生成的标识码,但区别于Session,Token是一种无状态的、由服务端签发但并不存放在服务端的认证方法。对于客户端传来的token服务端只需通过算法解码进行认证,以此来保持与用户的会话。相当于使用一部分的CPU计算资源换取了实际的存储开销,同时也解决了扩展性、跨域等等的问题。

SpringBoot项目中使用Token

使用Token做登录认证以及用户授权的最佳方案是使用JWT+Shiro,这里可以参考下面两篇文章:

由于本人的项目不涉及这么多,就没有采用上述的JWT+Shiro的方法(其实是自己菜),使用的是随机生成的token+Spring的拦截器简单实现的。。下面是代码:

1.Spring拦截器

拦截器用于对请求进行拦截,为SpringAOP思想的主要实践之一。主要用在登录之后检查token的合法性,从而判断请求是否合法,也可以进行权限访问控制等等(也可以用fiter)。
InterceptorConfig.java

@Configuration
class InterceptorConfig implements WebMvcConfigurer
{
    @Bean
    public TokenInterceptor initAuthInterceptor()
    {
        return new TokenInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(initAuthInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");//login放行,其他拦截
    }
}

对于/login放行,不检查token,其他请求均进行拦截。

TokenInterceptor.java

public class TokenInterceptor implements HandlerInterceptor
{
    @Autowired
    private JedisPool jedisPool;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
    {
        Jedis jedis = jedisPool.getResource();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=utf-8");
        String token = request.getHeader("token");
        if (token.isEmpty()) {
            response.getWriter().print("用户未登录,请登录后操作");
            return false;
        }
        if(!jedis.exists(token)){
            response.getWriter().print("登录状态已过期,请重新登录");
            return false;
        }
        int identity = Integer.parseInt(jedis.hget(token,"auth"));
        String url = request.getRequestURI();
        if(identity==2){
            if(url.startsWith("/course")||url.startsWith("/elective")||url.startsWith("/group")||url.startsWith("/module")
                    ||url.startsWith("/project")||url.startsWith("/school")||url.startsWith("/class")||url.startsWith("/students")
                    ||url.startsWith("/teachers")||url.startsWith("/test")){
                response.getWriter().print("你没有权限,无法操作");
                return false;
            }
        }
        jedis.close();
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

在preHandle里做请求的拦截,作用于Controller调用之前,返回true为放行,false为立即返回response。
其实可以看出我生成的token是存放在redis里的。。。(这根session有什么区别啊喂!!)(溜)

2.登录

Login.java

@RestController
public class Login
{
    @Autowired
    private IAccountService accountService;
    @Autowired
    private ITeacherService teacherService;
    @Autowired
    private JedisPool jedisPool;

    /**
     * 登录功能
     * @param username 账号
     * @param password 密码
     */
    @RequestMapping(value = "login",method = RequestMethod.POST)
    public Object login(String username,String password)
    {
        Jedis jedis = jedisPool.getResource();
        if(!accountService.existAccount(username)) return "账号不存在";//账号不存在
        Account account = accountService.getAccount(username);
        String psw = account.getPassword();
        if(!password.equals("123")){
            password = new Md5Hash(password).toString();
        }
        Map<String,String> map = new HashMap<>();
        Integer identity = account.getIdentity();
        String token = UUID.randomUUID().toString();
        if(identity==1){
            if(password.equals(psw)) {
                map.put("token",token);
                map.put("identity","student");
                jedis.hset(token,"id",account.getUsername());
                jedis.hset(token,"auth",account.getIdentity().toString());
            }
            else return "密码错误,请重新输入";
        }else{
            if(password.equals(psw)){
                jedis.hset(token,"id",teacherService.queryTeacherByUsername(account.getUsername()).getId().toString());
                jedis.hset(token,"auth",account.getIdentity().toString());
                map.put("token",token);
                map.put("identity","teacher");
            }
            else return "密码错误,请重新输入";
        }
        jedis.expire(token,6*3600);
        jedis.save();
        jedis.close();
        return map;
    }
    
	/**
     * 退出登录
     */
    @RequestMapping(value = "logout",method = RequestMethod.POST)
    public String logout(@RequestHeader("token")String token)
    {
        Jedis jedis = jedisPool.getResource();
        jedis.del(token);
        jedis.close();
        return "0";
    }
}

登录时生成随机token,一边作为key保存用户数据放在redis里,设置过期时间;一边返回给前端,前端储存在localStorage里。登出时直接删除key即可。
对于任意的Controller,无需传入userId,只需用@RequestHeader(“token”)拿到token后在redis中查询数据即可。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值