• SpringSecurity +JWT 实现前后端分离的登录


    SpringSecurity +JWT 实现前后端分离的登录

    要实现前后端分离,需要考虑以下2个问题:

    • 项目不再基于session了,如何知道访问者是谁?

    • 如何确认访问者的权限?

    前后端分离,一般都是通过token实现,本项目也是一样;用户登录时,生成token及token过期时间,token与用户是一一对应关系,调用接口的时候,把token放到header或请求参数中,服务端就知道是谁在调用接口,登录如下所示:

    image-20210714225747989

    SpringSecurity登录认证流程:https://blog.csdn.net/weixin_44588495/article/details/105907312

    image-20210714231154013

    1.搭建步骤

    1. 引入依赖

    <!-- springboot security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- jwt -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <dependency>
        <groupId>com.github.axet</groupId>
        <artifactId>kaptcha</artifactId>
        <version>0.0.9</version>
    </dependency>
    <!--hutools工具类-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.7.3</version>
    </dependency>
    

    2.application.properties配置文件

    #spring security
    spring.security.user.password=123456
    spring.security.user.name=admin
    #jwt
    jwt.header=Authorization
    #过期时间设置为7天
    jwt.expire=7 
    #32位的字符
    jwt.secret=ji8n3439n439n43ld9ne9343fdfer49h 
    
    

    这里使用配置文件的方式配置用户名和密码,比较简单便于测试,实际项目中是从数据库取用户数据

    3.创建JWT工具类

    该工具类主要包括3个方法,jwt的token的生成和解析以及token的过期与否

    @Component
    @Data
    @ConfigurationProperties(prefix = "jwt")
    public class JwtUtil {
    
        private int expire;
        private String secret;
        private String header;
        /**
         * 生成jwt
         * @param userName
         * @return
         */
        public String generateToken(String userName)
        {
            Date nowDate = new Date();
            Date expireDate = DateUtil.offsetDay(nowDate,expire);//设置过期时间
    
            return Jwts.builder()
                    .setHeaderParam("","")
                    .setSubject(userName)
                    .setIssuedAt(nowDate)
                    .setExpiration(expireDate)
                    .signWith(SignatureAlgorithm.HS256,secret)
                    .compact();
        }
    
        /**
         * 解析JWT
         * @param jwt
         * @return
         */
        public Claims parseClaim(String jwt)
        {
            Claims claims = null;
            try {
                claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(jwt).getBody();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return claims;
        }
        /**
         *jwt是否过期
         *
         */
        public boolean isTokenExpired(Claims claims) {
            return claims.getExpiration().before(new Date());
        }
    }
    

    便于后续的配置,这里成员变量的值都写到配置文件中

    4.创建SecurityConfig配置文件

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Resource
        private LoginFailureHandler loginFailureHandler;
        @Resource
        private LoginSuccessHandler loginSuccessHandler;
        @Resource
        private CaptchaFilter captchaFilter;
    
     /*   @Bean
        public BCryptPasswordEncoder bCryptPasswordEncoder()
        {
            return new BCryptPasswordEncoder();
        }*/
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors().and().csrf().disable()
    
                    .formLogin()
                    .successHandler(loginSuccessHandler)//登录成功后的处理,会生成jwt的token并返回
                    .failureHandler(loginFailureHandler)//登录失败后的处理
    
                    .and()//关闭session
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    
                    .and()//配置安全访问规则
                    .authorizeRequests()
                    .antMatchers("/login","/captcha","/logout","/favicon.ico").permitAll()
                    .anyRequest().authenticated()
    
                    //添加自定义验证码过滤器,在UsernamePasswordAuthenticationFilter之前
                    .and()
                    .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }
    

    5.登录成功和失败处理类

    • LoginSuccessHandler

      LoginSuccessHandler类实现接口AuthenticationSuccessHandler,并且重写onAuthenticationSuccess方法,在方法中实现对登录成功的逻辑处理,生成lwt并且将jwt放在请求头中返回给前端

      @Component
      public class LoginSuccessHandler implements AuthenticationSuccessHandler {
      
          @Autowired
          private JwtUtil jwtUtil;
      
          @Override
          public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
      
              httpServletResponse.setContentType("application/json;charset=UTF-8");
              ResponseResult responseResult = ResponseResult.createBySuccessMessage("登录成功");
            //生成jwt
              String jwt = jwtUtil.generateToken(authentication.getName());
                //把生成的jwt放在请求头中返回,前端以后访问后端接口请求头都需要带上它
              httpServletResponse.setHeader(jwtUtil.getHeader(),jwt);
      
              ServletOutputStream outputStream = httpServletResponse.getOutputStream();
              outputStream.write(JSONUtil.toJsonStr(responseResult).getBytes("UTF-8"));
      
              outputStream.flush();
              outputStream.close();
          }
      }
      
    • LoginFailureHandler

      LoginFailureHandler实现接口AuthenticationFailureHandler并且实现onAuthenticationFailure方法,在方法中实现对登录失败的逻辑处理。

      @Slf4j
      @Component
      public class LoginFailureHandler implements AuthenticationFailureHandler {
          @Override
          public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
      
              httpServletResponse.setContentType("application/json;charset=UTF-8");
      
              ResponseResult responseResult =ResponseResult.createByErrorMessage(e.getMessage());
      
              ServletOutputStream outputStream = httpServletResponse.getOutputStream();
              outputStream.write(JSONUtil.toJsonStr(responseResult).getBytes("UTF-8"));
      
              outputStream.flush();
              outputStream.close();
          }
      }
      

    2.认证失败异常处理

    由于我们的securityConfig的配置文件中只是对部分的url放行不做认证,其他的访问请求都需要做认证。比如我们现在访问localhost:8081/sys/menu/nav这个请求,认证失败后返回的结果是给我们跳转到内置的一个登录页面,因为这是一个前后端分离的,这不是我们希望的结果,我们希望的结果是返回一个统一格式的返回结果给前端。

    image-20210715113032541

    这里需要配置我们的AuthenticationEntryPoint,从官方文档解释如下

    image-20210716135025379

    因此我们需要创建一个类JwtAuthenticationEntryPoint类实现AuthenticationEntryPoint接口,并实现该方法commence

    image-20210716135308588

    然后在配置文件securityConfig中,注入该配置

    image-20210716135422661

    如此设置后访问没有认证的接口也就不会再跳转到登陆页面,而是给前端返回一个统一的json数据格式。

    3.登陆测试

    使用postman进行登陆测试,首先发送请求获取验证码,后台生成验证码的同时,将该验证码存储到redis中,并将redis存储的验证码的key给返回,即token=key,此token非JWT生成的token, JWT生成的token是在登陆成功后才生成的。

    image-20210720144127020

    获取到验证码后,进行登陆接口测试。

    image-20210720143803613

  • 相关阅读:
    过往总结
    查找光标处的标识符
    【转】Linux 内核开发 Eclipse内核开发环境搭建
    【转】Writing linux kernel code in Eclipse
    【转】 Linux内核升级指南
    [转]Ubuntu 11.04 安装后要做的20件事情
    【转】vim 替换操作大全
    【转】移动硬盘安装ubuntu
    重置 Winsock 目录
    【转】让Firefox像vim一样操作
  • 原文地址:https://www.cnblogs.com/seanRay/p/15034906.html
Copyright © 2020-2023  润新知