0711~SpringSecurity源码查看,springsecurity整合JWT

过滤器执行流程图:

~过滤器链

 今天增加一个短信验证码的功能,自定义过滤器实现;

1. 首先编写短信验证码的接口,发送短信验证码;

2.写一个过滤器类,继承OncePerRequestFilter,主要用来做验证短信的过滤 ;

3.自定义一个过滤器类,实现AbstractAuthenticationProcessingFilter接口,用来把手机号和验证码封装到token对象,交给认证管理器;

4.自定义一个过滤器类,实现AuthenticationProvider接口,把权限字符串交给security管理;

1. 首先编写短信验证码的接口,发送短信验证码;

@RestController
@Slf4j
public class SmsCodeController {

    @RequestMapping("/sms/send/{phone}")
    public void getSmsCode(@PathVariable("phone")String phone, HttpServletRequest request){
        //发送验证码
        String uuid = UUID.randomUUID().toString().substring(0,4);
        log.info("验证码是,{}",uuid);
        //把验证消息存入session
        request.getSession().setAttribute("token",uuid);
    }

}

 2.写一个过滤器类,继承OncePerRequestFilter,主要用来做验证短信的过滤 ;

@Component
public class SmsCodeCheckFilter extends OncePerRequestFilter {

    private AntPathMatcher antPathMatcher =  new AntPathMatcher();

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        //1.判断是不是登录请求
        boolean match = antPathMatcher.match("/smslogin", request.getRequestURI());
        if(match==false){
            filterChain.doFilter(request,response);
            return;
        }
        //2.如果是登录请求,从session里面拿到token
        String token = (String)request.getSession().getAttribute("token");
        if(!StringUtils.hasLength(token)){
            myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("验证码过期了"));
            return;
        }
        //3.再拿到用户输入的code
        String code = request.getParameter("code");
        if(!StringUtils.hasLength(code)){
            myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("验证码不能为空"));
            return;
        }
        String phone = request.getParameter("phone");
        if(!StringUtils.hasLength(phone)){
            myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("手机号不可为空"));
            return;
        }
        //4.再进行比对,如果正确则直接返回
        if(!code.equalsIgnoreCase(token)){
            myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("验证码不正确"));
            return;
        }
        //5.如果正确则执行后面的filter
        filterChain.doFilter(request,response);
    }
}

 3.自定义一个过滤器类,实现AbstractAuthenticationProcessingFilter接口,用来把手机号和验证码封装到token对象,交给认证管理器;

public class SmsAuthFilter extends AbstractAuthenticationProcessingFilter {

    private boolean postOnly = true;

    public SmsAuthFilter() {
        super(new AntPathRequestMatcher("/smslogin","POST"));
    }

    @Override
    public Authentication attemptAuthentication(
            HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        //拿到前端输入的手机号和验证码
        String phone = request.getParameter("phone");
        String code = request.getParameter("code");

        if (phone == null) {
            phone = "";
        }

        if (code == null) {
            code = "";
        }

        phone = phone.trim();
        //封装token
        SMSAuthenticationToken authRequest = new SMSAuthenticationToken(phone,code,null);

        setDetails(request,authRequest);
        //交给认证管理器
        return this.getAuthenticationManager().authenticate(authRequest);
    }
    protected void setDetails(HttpServletRequest request,
                              SMSAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
}

 4.自定义一个过滤器类,实现AuthenticationProvider接口,把权限字符串交给security管理;

public class SmsDaoAuthenticationProvider implements AuthenticationProvider {

    private MyUserDetailsService myUserDetailsService;

    public void setMyUserDetailsService(MyUserDetailsService myUserDetailsService) {
        this.myUserDetailsService = myUserDetailsService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //通过手机号查询权限集合
        String phone = (String)authentication.getPrincipal();
        UserDetails userDetails = myUserDetailsService.loadUserByUsername(phone);
        //再封装到token里面
        SMSAuthenticationToken smsAuthenticationToken = new SMSAuthenticationToken(phone,null,userDetails.getAuthorities());
        //获取sessionID,为下次访问做个标识
        smsAuthenticationToken.setDetails(authentication.getDetails());
        return smsAuthenticationToken;
    }

    //判断是验证码还是账号登录 为true就是短信验证码登录执行以上方法
    //                     为false就是密码登录执行以上方法
    @Override
    public boolean supports(Class<?> aClass) {
        return (SMSAuthenticationToken.class.isAssignableFrom(aClass));
    }
}

 自定义过滤器类之后需要用辅助配置类,把过滤器关联起来,然和辅配置类再和主配置类进行关联;

辅助配置类如下:

@Configuration
public class SmsAuthConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    private MyAuthenticationSuccessHandler handler;
    @Autowired
    private MyAuthenticationSuccessHandlerSuccess handlerSuccess;

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Autowired
    private SmsCodeCheckFilter smsCodeCheckFilter;

    @Override
    public  void configure(HttpSecurity http) throws Exception {
        //短信验证码登录=========================================================================================start
        //添加认证Filter
        SmsAuthFilter smsAuthFilter = new SmsAuthFilter();
        smsAuthFilter.setAuthenticationSuccessHandler(handlerSuccess);
        smsAuthFilter.setAuthenticationFailureHandler(handler);

        //设置认证管理器
        smsAuthFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));

        //将MyUserDetailsService通过set方式注入到SmsDaoAuthenticationProvider中
        SmsDaoAuthenticationProvider smsDaoAuthenticationProvider = new SmsDaoAuthenticationProvider();
        smsDaoAuthenticationProvider.setMyUserDetailsService(myUserDetailsService);

        //添加短信认证provider
        http.authenticationProvider(smsDaoAuthenticationProvider);

        //添加SmsCodeCheckFilterr 到Security过滤器链中
        http.addFilterBefore(smsCodeCheckFilter, UsernamePasswordAuthenticationFilter.class);

        //添加 smsAuthFilter 到Security过滤器链中
        http.addFilterAfter(smsAuthFilter, UsernamePasswordAuthenticationFilter.class);

        //短信验证码登录=========================================================================================end
    }
}

 和主配置类进行关联,用apply方法;

  @Autowired
    private SmsAuthConfig smsAuthConfig;



http.apply(smsAuthConfig);

 SpringSecurity整合JWT;

JWT是什么?

全称为:json web token ,是一个轻巧级的规范;使得再用户与服务之间的信息传递安全可靠;

JWT优点:

1.基于json,方便解析;

2.可令牌中自定义内容,方便解析;

JWT有三部分组成;

1.头部;

2.载荷;

3.签名;

其格式为两点三段式;

整合步骤:

1.导入依赖;

2.登录成功之后携带token;

3.每次请求过来时校验token(自定义一个过滤器类,继承BasicAuthenticationFilter类);

4.把权限集合交给security管理(在主配置类里面的配置 );

 1.导入依赖;

 <!-- jjwt包 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

 2.登录成功之后携带token;

@Component
public class MyAuthenticationSuccessHandlerSuccess implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        //1.获取权限集合和用户名称;
        Collection<? extends GrantedAuthority> list = authentication.getAuthorities();
        String name = authentication.getName();
        //2.封装成map返回
        HashMap<String, Object> userMap = new HashMap<>();
        userMap.put("name",name);
        userMap.put("authList",list);
        //3.设置私密生成令牌
        String path = ResourceUtils.getFile("D:\\java\\rsa.pri").getPath();
        try {
            PrivateKey key = RsaUtils.getPrivateKey(path);
            String tokenExpireInMinutes = JwtUtils.generateTokenExpireInMinutes(userMap, key, 60);
        response.getWriter().print(JSONObject.toJSONString(JSONResult.success(tokenExpireInMinutes)));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

3.每次请求过来时校验token(自定义一个过滤器类,继承BasicAuthenticationFilter类);

public class MyBasicAuthenticationFilter extends BasicAuthenticationFilter {
    public MyBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    public MyBasicAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
    }
    //注入成功结果集处理结果
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    public void setMyAuthenticationSuccessHandler(MyAuthenticationSuccessHandler myAuthenticationSuccessHandler) {
        this.myAuthenticationSuccessHandler = myAuthenticationSuccessHandler;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        //先选择需要放行的路径,不需要token的路径
       String[] uri =  new String[]{"/smslogin","/login","/sms/send"};
        String requestURI = request.getRequestURI();
        for(int i=0; i<uri.length; i++){
            if(requestURI.contains(uri[i])){
                //如果前端传过来的路径包含这些不需要token的路径
                chain.doFilter(request,response);
                return;
            }
       }
        String token = request.getHeader("token");
        if(!StringUtils.hasLength(token)){
            myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("没有令牌,请去登录"));
            return;
        }
        //到这里就需要验证token了
        try {
            SMSAuthenticationToken authentication = getAuthentication(request);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (Exception e) {
            e.printStackTrace();
            myAuthenticationSuccessHandler.onAuthenticationFailure(request,response,new UsernameNotFoundException("token验证错误!!!"));
            return;
        }

    }

    /**
     * 解析token,获取用户信息
     * @param request
     * @return
     */
    private SMSAuthenticationToken getAuthentication(HttpServletRequest request) throws  Exception {
        //获取前端传来的token
        String token = request.getHeader("token");
        if (token != null) {
            //通过token解析出载荷信息
            Map map = (Map) JwtUtils.getInfoFromToken(token, RsaUtils.getPublicKey(ResourceUtils.getFile("D:\\Java\\rsa.pub").getPath()),
                    Map.class);

            //取出token中的权限信息
            List<Map> auths = (List) map.get("authList");

            //定义一个临时的集合(user:add,user:delete)
            List auth_temp = new ArrayList();

            for (Map auth : auths) {
                auth_temp.add(auth.get("authority"));
            }
            //封装成权限集合
            Collection<? extends GrantedAuthority> grantedAuthorities =
                    AuthorityUtils.commaSeparatedStringToAuthorityList(org.apache.commons.lang3.StringUtils.join(auth_temp,','));
            //解析出jwt令牌中携带的用户权限列表,然后将其重写按照security的要求,封装为security能识别的token令牌
            return new SMSAuthenticationToken(null, null, grantedAuthorities);
        }
        return  null;
    }

}

 4.把权限集合交给security管理;(在主配置类里面的配置 )

//校验token的过滤器
        MyBasicAuthenticationFilter myBasicAuthenticationFilter = new MyBasicAuthenticationFilter(authenticationManager());
        myBasicAuthenticationFilter.setMyAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
        http.addFilter(myBasicAuthenticationFilter);

 

 

 

 
 
 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值