前言
這章我們來整合JWT,實現一個自定義的登入
一、認證流程
我先捋一下認證的流程,方便我們後面寫自定義登入
核心的類就幾個,分別是:
Authentication:使用者認證
AbstractAuthenticationProcessingFilter:認證處理攔截器
AuthenticationManager:處理認證
AuthenticationProvider:具體做認證的
UserDetailsService:獲取使用者資訊
AuthenticationSuccessHandler:認證成功處理器
AuthenticationFailureHandler:認證失敗處理器
我們自定義登入其實也是就是根據我們自己的需求重寫這幾個類。
二、自定義登入
認證和授權相關的都放在base-security這個目錄,方便我們後面做擴充套件;
自定義的這些類,其實就是仿照以UsernamePassword開頭的類來寫的,部分程式碼其實都是一樣的。
1、自定義使用者認證的物件JwtUser
public class JwtUser extends User {/*** 使用者ID*/@Getterprivate String id;/*** 機構ID*/@Getterprivate String orgId;public JwtUser(String id, String orgId, String username, String password, Collection<? extends GrantedAuthority> authorities) {super(username, password, authorities);this。id = id;this。orgId = orgId;}}
2、自定義JwtAuthenticationToken
程式碼其實跟UsernamePasswordAuthenticationToken差不多
public class JwtAuthenticationToken extends AbstractAuthenticationToken {/*** 登入資訊*/private final Object principal;/*** 憑證*/private final Object credentials;/*** 建立已認證的授權** @param authorities* @param principal* @param credentials*/public JwtAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object principal, Object credentials) {super(authorities);this。principal = principal;this。credentials = credentials;super。setAuthenticated(true);}/*** 建立未認證的授權** @param principal* @param credentials*/public JwtAuthenticationToken(Object principal, Object credentials) {//因為剛開始並沒有認證,因此使用者沒有任何許可權,並且設定沒有認證的資訊(setAuthenticated(false))super(null);this。principal = principal;this。credentials = credentials;super。setAuthenticated(false);}@Overridepublic Object getCredentials() {return this。credentials;}@Overridepublic Object getPrincipal() {return this。principal;}}
3、自定義認證攔截器JwtAuthenticationFilter
這個類也是仿照UsernamePasswordAuthenticationFilter來實現的
/*** 這個程式碼完全是仿照UsernamePasswordAuthenticationFilter來寫的* {@link UsernamePasswordAuthenticationFilter}*/public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(“/login”,“POST”);private String usernameParameter = “username”;private String passwordParameter = “password”;public JwtAuthenticationFilter() {super(DEFAULT_ANT_PATH_REQUEST_MATCHER);}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (!request。getMethod()。equals(HttpMethod。POST。name())) {throw new AuthenticationServiceException(“Authentication method not supported: ” + request。getMethod());}String username = request。getParameter(this。usernameParameter);username = (username != null) ? username。trim() : “”;String password = request。getParameter(this。passwordParameter);password = (password != null) ? password : “”;//建立未認證的tokenJwtAuthenticationToken authRequest = new JwtAuthenticationToken(username, password);//認證詳情寫入到憑著authRequest。setDetails(authenticationDetailsSource。buildDetails(request));return this。getAuthenticationManager()。authenticate(authRequest);}}
4、自定義認證處理器JwtAuthenticationProvider
大部分的程式碼也來自AbstractUserDetailsAuthenticationProvider和DaoAuthenticationProvider
@Slf4j@Componentpublic class JwtAuthenticationProvider implements AuthenticationProvider {private MessageSourceAccessor messages = SpringSecurityMessageSource。getAccessor();@Getter@Setterprivate UserDetailsService userDetailsService;@Getter@Setterprivate PasswordEncoder passwordEncoder;public JwtAuthenticationProvider() {}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {UserDetails user = userDetailsService。loadUserByUsername(authentication。getName());JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;additionalAuthenticationChecks(user, jwtAuthenticationToken);//構建已認證的authenticatedTokenJwtAuthenticationToken result = new JwtAuthenticationToken(jwtAuthenticationToken。getAuthorities(), user, jwtAuthenticationToken。getCredentials());result。setDetails(authentication。getDetails());log。debug(“Authenticated user”);return result;}@Overridepublic boolean supports(Class<?> authentication) {return (JwtAuthenticationToken。class。isAssignableFrom(authentication));}/*** 直接複製的DaoAuthenticationProvider裡面的同名方法* @param userDetails* @param authentication* @throws AuthenticationException*/private void additionalAuthenticationChecks(UserDetails userDetails,JwtAuthenticationToken authentication) throws AuthenticationException {if (authentication。getCredentials() == null) {log。debug(“Failed to authenticate since no credentials provided”);throw new BadCredentialsException(this。messages。getMessage(“AbstractUserDetailsAuthenticationProvider。badCredentials”, “Bad credentials”));}String presentedPassword = authentication。getCredentials()。toString();if (!this。passwordEncoder。matches(presentedPassword, userDetails。getPassword())) {log。debug(“Failed to authenticate since password does not match stored value”);throw new BadCredentialsException(this。messages。getMessage(“AbstractUserDetailsAuthenticationProvider。badCredentials”, “Bad credentials”));}}}
5、自定義認證成功和失敗處理類
預設情況下,認證成功和失敗都是跳轉到別的頁面,我們改為返回一個json物件
5。1、認證失敗JwtAuthenticationFailureHandler
@Slf4j@Componentpublic class JwtAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {log。info(“登入失敗:{}”, exception。getLocalizedMessage());response。setContentType(MediaType。APPLICATION_JSON_VALUE);response。getWriter()。write(exception。getLocalizedMessage());response。getWriter()。flush();response。getWriter()。close();}}
5。2、認證成功JwtAuthenticationSuccessHandler
認證成功後我們需要返回一個token,所以我們需要一個Jwt的工具類JWTUtils
@Slf4j@Component@AllArgsConstructorpublic class JWTUtils {private final JwtProperties jwtProperties;public static final String ID = “id”;public static final String ORGID = “orgId”;public static final String USERNAME = “username”;public static final String AUTHORITIES = “authorities”;/*** 生成token** @param jwtUser* @return*/public String createToken(JwtUser jwtUser) {// 簽名演算法 ,將對token進行簽名SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm。HS256;byte[] apiKeySecretBytes = DatatypeConverter。parseBase64Binary(jwtProperties。getSecret());Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm。getJcaName());Map
這裡面的jwtProperties主要用來動態配置token秘鑰和有效期,所以需要在spring。factories配置
@Slf4j@Componentpublic class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Autowiredprivate JWTUtils jwtUtils;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {//從authentication中獲取使用者資訊final JwtUser userDetail = (JwtUser) authentication。getPrincipal();log。info(“{}:登入成功”, userDetail。getUsername());response。setContentType(MediaType。APPLICATION_JSON_VALUE);String token = jwtUtils。createToken(userDetail);response。getWriter()。write(token);response。getWriter()。flush();response。getWriter()。close();}}
6、安全配置
@EnableWebSecuritypublic class SpringSecurityConfigurer {private final JwtUserDetailsService jwtUserDetailsService;private final JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandle;private final JwtAuthenticationFailureHandler jwtAuthenticationFailureHandler;public SpringSecurityConfigurer(JwtUserDetailsService jwtUserDetailsService, JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandle, JwtAuthenticationFailureHandler jwtAuthenticationFailureHandler) {this。jwtUserDetailsService = jwtUserDetailsService;this。jwtAuthenticationSuccessHandle = jwtAuthenticationSuccessHandle;this。jwtAuthenticationFailureHandler = jwtAuthenticationFailureHandler;}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http//禁用表單登入。formLogin()。disable()。authorizeRequests((authorize) -> authorize// 這裡需要將登入頁面放行,permitAll()表示不再攔截,。antMatchers(“/upms/login/**”)。permitAll()// 所有請求都要驗證。anyRequest()。authenticated())// 關閉csrf。csrf((csrf) -> csrf。disable())//禁用session,JWT校驗不需要session。sessionManagement((session) -> session。sessionCreationPolicy(SessionCreationPolicy。STATELESS));http。addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter。class);return http。build();}@BeanJwtAuthenticationFilter jwtAuthenticationFilter() {JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter();jwtAuthenticationFilter。setAuthenticationManager(authenticationManager());jwtAuthenticationFilter。setAuthenticationSuccessHandler(jwtAuthenticationSuccessHandle);jwtAuthenticationFilter。setAuthenticationFailureHandler(jwtAuthenticationFailureHandler);return jwtAuthenticationFilter;}@BeanJwtAuthenticationProvider jwtAuthenticationProvider() {JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider();//設定userDetailsServicejwtAuthenticationProvider。setUserDetailsService(jwtUserDetailsService);//設定加密演算法jwtAuthenticationProvider。setPasswordEncoder(passwordEncoder());return jwtAuthenticationProvider;}/*** 自定義的認證處理器*/@Beanpublic AuthenticationManager authenticationManager() {return new ProviderManager(jwtAuthenticationProvider());}/*** 指定加解密演算法** @return*/@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}
三、編譯執行
經過一系列的除錯修改後,啟動專案,模擬登入請求,看到如下介面就表示成功了。
當前版本tag:1。0。3
程式碼倉庫
四、 體驗地址
後臺資料庫只給了部分許可權,報錯屬於正常!
想學的老鐵給點點關注吧!!!
我是阿咕嚕,一個從網際網路慢慢上岸的程式設計師,如果喜歡我的文章,記得幫忙點個贊喲,謝謝!
猜你喜歡
- 2023-01-11實戰Netty!基於私有協議,怎樣快速開發網路通訊服務
- 2022-12-01netty入門到彈幕實戰
- 2021-12-14Callable和Future
- 2021-12-11南京|2019首場WSET2級,開始報名了
- 2021-04-222021產品經理的你這幾個方面請及時提升