spring security +jwt +mybatisPlus搭建

前言

最近花了两天复习了一下spring security,在网上找了很多帖子和视频,感觉这些帖子感觉讲解的很零散,毕竟spring security里面默认内置了很多场景的解决方案。这里我们只关注spring security + jwt + mybaitsPlus 的解决方案。此方案是我找了很多视频帖子最后总结出来的,最后我参考了ruoyi项目和spring-security-plus,发现这种方式和我自己总结的大同小异,就决定吧这种方式记录下来,这里我们只把和spring security认证和授权的部分拿出来

说明

若想直接用一个权限管理的脚手架,建议直接用rouyi,自己搭太累了(别问我怎么知道的)

  • 首先我们要明确安全框架主要有两个部分: 认证 和 授权
  1. 认证是什么 :我们可以理解为登录功能
  2. 授权是什么 :授权就是spring security会将你规定的权限和用户带有的权限进行匹配,若校验成功则授权成功
登录
  • 接着我们思考一下登录需要什么
    1. 输入用户名密码
    2. 将输入的密码进行校验
    3. 未登录获取资源怎么办
    4. 怎么验证token,验证token后怎么让spring security知道认证通过了
  • 我们再来想一下code时候需要做什么
    1. 密码暗文加密
    2. 如何将用户信息封装起来

接下来我们了解一下spring security 都有哪些组件(注意仅针对使用jwt的方式)
我们从一个完整的从登录到认证的完整流程中了解spring security组件

  1. 首先输入用户名密码登录,登录会调用/login(注意这里是自定义的方法,不是默认的/login,在强调一次仅针对使用jwt的方式,为什么要这么强调呢,因为我是先搭了一个jwt,然后看的spring security视频,蒙了很久)

这个方法调用service的login,获得令牌,将令牌返回

@PostMapping("/login")
    public AjaxResult login(@RequestBody LoginBody loginBody) {
    	//我当时也是起名叫AjaxResult的,好巧
        AjaxResult ajax = AjaxResult.success();
        // 生成令牌
        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
                loginBody.getUuid());
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }
public String login(String username, String password, String code, String uuid) {
    	//查看缓存中有没有已经保存过的信息
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null) {
            throw new CaptchaExpireException();
        }
        //是否禁用
        if (!code.equalsIgnoreCase(captcha)) {
            throw new CaptchaException();
        }
        // 用户验证
        Authentication authentication = null;
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
        authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));

        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        // 生成token
        return tokenService.createToken(loginUser);
    }
  • 这里我们看到了第一个spring security 组件 UserDetailsService 他提供了一个loadUserByUsername(username) 方法,可以实现这个方法,根据用户名从数据库取出用户信息
@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    	//根据用户名取出user
        SysUser user = userService.selectUserByUserName(username);
        return createLoginUser(user);
    }
  • 还是在service里有第二个组件 authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));这个方法就会去调用loadUserByUsername得到数据库数据中的密码与参数(个 就这个)进行匹配,匹配成功后将用户信息取出保存到token里,返回token 登录部分完成
  1. 认证部分(我们需要解析token来判断是否是登录状态),这个实现很简单,我们知道spring security是通过过滤器链的方式来进行控制的,我们只需要在合适的地方加入一个解析jwt的过滤器即可,我们先不管他加到哪里,先来看一下做了什么
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
    @Autowired
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException{
         //从token中获得用户信息(在登录的时候把用户信息放到了token里返回给客户端,客户端发送请求的时候就会带着token)
        LoginUser loginUser = tokenService.getLoginUser(request);
        
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
    		// 获取到用户信息的话就将用户信息封装到UsernamePasswordAuthenticationToken里放入上下文
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}
  • 我们来看一下UsernamePasswordAuthenticationToken 类,我们只看他两个构造器就行了,其他都是getter和setter
// 可以看到两个参数构造器会让认证失败
	public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		setAuthenticated(false);
	}
// 三个参数构造器会认证成功
	public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
			Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		super.setAuthenticated(true); // must use super, as we override
	}
  • 所以我们通过创建三个参数构造器的UsernamePasswordAuthenticationToken的参数就可以让spring security 认为认证成功(能取出用户信息就说明jwt已经验证通过了)

  • 这样我们认证流程就完成了,我们看一下这个filter方到了那里,找到security config

// 加载了UsernamePasswordAuthenticationFilter.class过滤器前面(后面那个只是个代表个位置和前面没有任何关系)
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  • 现在我们回过来看一下登录失败怎么办,在spring security 登录失败会抛出异常,异常会被AuthenticationEntryPoint处理,我们只需要实现它的方法给他一个处理方式即可,我们来写一个登录失败的处理器
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
            throws IOException
    {
        int code = HttpStatus.UNAUTHORIZED;
        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
        ServletUtils.renderString(response, 
        //写回一个json
JSON.toJSONString(AjaxResult.error(code, msg)));
    }
}

将他配置到security config 中

@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
...
httpSecurity.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
...
授权

在从数据库拿到用户信息的里面已经包含了权限信息,所以我们从token中取到的用户信息里面已经带了权限信息,那么spring security 是如何授权的呢,spring security 提供了两种方式,一种是在config配置中配置,一种是使用注解方式,由于这里我学的时候没有什么很难搞清楚的地方就略了

最后聊一聊

认证这里很乱是因为认证方式比较灵活

  1. 可以将用户信息在每次认证的时候再从数据库去,登陆的时候token只存username,这样可以热部署,但是每次都会查询数据库
  2. token里面已经带了用户所有信息,认证的时候只要jwt验证通过就算认证成功,这样不用每次都查数据库,但是这样会导致token很大
  3. 还可以token存放一个标识,认证的时候jwt验证通过后取出标识,从redis中根据标识取出用户信息,能取出用户信息就算认证成功 --> ruoyi 用的是这种
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值