2021-01-24 SpringBoot + Spring Security + JWT 实现认证鉴权

前言

1、会基本的Spring Security配置
2、大概了解Spring Security原理(一条过滤器链)

简单理解

Spring Security就是一条过滤器链,如果你登录了,那么会有过滤器将你的认证信息解析出来并放到Security的上下文中,这样其他过滤器就通过这个认证信息来鉴权


JWT配置
  • 简单描述

之前由Security的过滤器来解析认证信息,现在我们自己定义一个过滤器,将携带的token解析为认证信息即可

  • 登录:登录成功后生成token
//	cn.mb.itemdemo.controller.TestController
@GetMapping("/login")
public CommonResult login() {
	//	登录校验
    UserDetails userDetails = userService.loadUserByUsername("root");
    String token = jwtTokenUtil.generateToken(userDetails);
    return CommonResult.success(token);
}
  • 解析token过滤器
//	cn.mb.itemdemo.component.CustomTokenFilter
public class CustomTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        //  获取请求头
        String header = request.getHeader(jwtTokenUtil.getTokenHeader());
        //  解析token
        if (header != null && header.startsWith(jwtTokenUtil.getTokenHead())) {
            String token = header.substring(jwtTokenUtil.getTokenHead().length());
            //  获取用户名
            String username = jwtTokenUtil.getUserNameFromToken(token);
            //  每次都重新查询用户及其权限(保证动态权限)
            UserDetails userDetails = userService.loadUserByUsername(username);
            if (jwtTokenUtil.validateToken(token, userDetails)) {
                //  将用户信息放入SecurityContextHolder中
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        chain.doFilter(request, response);
    }

}
  • 在Security配置类中将过滤器注入到过滤链中
protected void configure(HttpSecurity http) throws Exception {
	//  自定义token解析器
    http.addFilterBefore(customTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
  • jwt小结

通过上述操作,即可实现JWT认证
其实只要知道token是在过滤链中解析成用户数据即可,后面的事就交给Security来做(一开始我也是懵的不知道怎么结合,其实跟用拦截器判断认证一样)


鉴权
  • 简单描述

FilterSecurityInterceptor是过滤器链的最后一个,在执行时就会做鉴权操作
但其依赖AccessDecisionManager.decide方法做实际鉴权操作
且依赖SecurityMetadataSource.getAttributes获取当前资源对应的权限
因此我们需要自定义上述三个对象并注入到Security中

  • 代码
    • 自定义鉴权过滤器:放行、调用鉴权
public class CustomAuthFilter extends AbstractSecurityInterceptor implements Filter {

    private final IgnoreUrlsConfig ignoreUrlsConfig;
    private final CustomMetadataSource customMetadataSource;

    public CustomAuthFilter(IgnoreUrlsConfig ignoreUrlsConfig, CustomMetadataSource customMetadataSource, CustomAccessDecisionManager customAccessDecisionManager) {
        this.ignoreUrlsConfig = ignoreUrlsConfig;
        this.customMetadataSource = customMetadataSource;
        super.setAccessDecisionManager(customAccessDecisionManager);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        //  OPTIONS请求直接放行
        if (request.getMethod().equals(HttpMethod.OPTIONS.toString())) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            return;
        }
        //  白名单请求直接放行
        PathMatcher pathMatcher = new AntPathMatcher();
        for (String path : ignoreUrlsConfig.getUrls()) {
            if (pathMatcher.match(path, request.getRequestURI())) {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
                return;
            }
        }
        //  此处会调用AccessDecisionManager中的decide方法进行鉴权操作
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return customMetadataSource;
    }
}
    • 自定义鉴权管理器:实际鉴权
public class CustomAccessDecisionManager implements AccessDecisionManager {

    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        // 当接口未被配置资源时直接放行
        if (CollUtil.isEmpty(configAttributes)) {
            return;
        }
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute configAttribute = iterator.next();
            //  将当前访问所需资源或用户拥有资源进行比对
            String needAuthority = configAttribute.getAttribute();
            for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
                //  如果有该权限直接放行
                if (needAuthority.trim().equals(grantedAuthority.getAuthority())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("抱歉,您没有访问权限");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}
    • 自定义获取权限源:获取当前资源对应的权限
public class CustomMetadataSource implements FilterInvocationSecurityMetadataSource {

    //  所有资源
    private List<String> allResource;

    @PostConstruct
    public void loadDataSource() {
        //  把所有权限加载到内存中
        allResource = new ArrayList<>();
        allResource.add("/add");
        allResource.add("/delete");
        allResource.add("/update");
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        List<ConfigAttribute>  configAttributes = new ArrayList<>();
        //  获取当前访问的路径
        String url = ((FilterInvocation) o).getRequestUrl();
        if (allResource.contains(url)) {
            configAttributes.add(new org.springframework.security.access.SecurityConfig(url));
        }
        return configAttributes;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}
    • 将过滤器注入过滤器链中
protected void configure(HttpSecurity http) throws Exception {
	//  自定义权限过滤器
    http.addFilterBefore(customAuthFilter, FilterSecurityInterceptor.class);
}
  • 原理:可以看demo[最后]中的鉴权.txt

小结

以上就是SpringBoot + Spring Security + JWT的认证授权过程了
一开始看还很懵他都是自己内置的页面,那前后分离怎么办
其实就像原本用拦截器做token校验一样,加个token过滤器解析成Security需要的认证信息即可,后面框架会来认证
而鉴权方面,则像上面一样做即可(PS:但我感觉直接在过滤器里面都可以完成鉴权操作了,只不过代码会有点长)


demo

demo


学习自


2021-02-25 鉴权操作

其实可以直接实现AccessDecisionManager并在配置类中注入,不用像上面那么麻烦再去实现过滤器,代码如下

protected void configure(HttpSecurity http) throws Exception {  
	http.authorizeRequests().accessDecisionManager(myAccessDecisionManager);
}
  • 自定义AccessDecisionManager:权限即url
public class CustomAccessDecisionManager implements AccessDecisionManager {

    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        String url = ((FilterInvocation) o).getRequestUrl();
        for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
            //  如果有该权限直接放行
            if (url.trim().equals(grantedAuthority.getAuthority())) {
                return;
            }
        }
        throw new AccessDeniedException("抱歉,您没有访问权限");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

Spring Security 工作原理概览

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值