1. 程式人生 > 其它 >SpringSecurity +JWT 實現前後端分離的登入

SpringSecurity +JWT 實現前後端分離的登入

SpringSecurity +JWT 實現前後端分離的登入

要實現前後端分離,需要考慮以下2個問題:

  • 專案不再基於session了,如何知道訪問者是誰?

  • 如何確認訪問者的許可權?

前後端分離,一般都是通過token實現,本專案也是一樣;使用者登入時,生成token及token過期時間,token與使用者是一一對應關係,呼叫介面的時候,把token放到header或請求引數中,服務端就知道是誰在呼叫介面,登入如下所示:

SpringSecurity登入認證流程:https://blog.csdn.net/weixin_44588495/article/details/105907312

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這個請求,認證失敗後返回的結果是給我們跳轉到內建的一個登入頁面,因為這是一個前後端分離的,這不是我們希望的結果,我們希望的結果是返回一個統一格式的返回結果給前端。

這裡需要配置我們的AuthenticationEntryPoint,從官方文件解釋如下

因此我們需要建立一個類JwtAuthenticationEntryPoint類實現AuthenticationEntryPoint介面,並實現該方法commence

然後在配置檔案securityConfig中,注入該配置

如此設定後訪問沒有認證的介面也就不會再跳轉到登陸頁面,而是給前端返回一個統一的json資料格式。

3.登陸測試

使用postman進行登陸測試,首先發送請求獲取驗證碼,後臺生成驗證碼的同時,將該驗證碼儲存到redis中,並將redis儲存的驗證碼的key給返回,即token=key,此token非JWT生成的token, JWT生成的token是在登陸成功後才生成的。

獲取到驗證碼後,進行登陸介面測試。