springboot security自定義認(rèn)證過程
前言
前置閱讀
說明
實際場景,我們一般是把用戶信息保存在db中(也可能是調(diào)用三方接口),需要自定義用戶信息加載或認(rèn)證部分的邏輯。
下面提供一個示例
代碼示例
定義用戶bean
@AllArgsConstructor @Data public class User { private String username; private String password; }
定義Mapper
示例,代碼寫死了,并不是實際從數(shù)據(jù)庫或某個存儲查詢用戶信息:
@Component public class UserMapper { public User select(String username) { return new User(username, "pass"); } }
定義加載用戶數(shù)據(jù)的類
UserDetailsService 是spring security內(nèi)置的加載用戶信息的接口,我們只需要實現(xiàn)這個接口:
@Slf4j @Component public class UserDetailsServiceImpl implements UserDetailsService { public static final UserDetails INVALID_USER = new org.springframework.security.core.userdetails.User("invalid_user", "invalid_password", Collections.emptyList()); private final UserMapper userMapper; public UserDetailsServiceImpl(UserMapper userMapper) { this.userMapper = userMapper; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 根據(jù)用戶名從數(shù)據(jù)庫查詢用戶信息 User user = userMapper.select(username); if (user == null) { /** * 如果沒查詢到這個用戶,考慮兩種選擇: * 1. 返回一個標(biāo)記無效用戶的常量對象 * 2. 返回一個不可能認(rèn)證通過的用戶 */ return INVALID_USER; // return new User(username, System.currentTimeMillis() + UUID.randomUUID().toString(), Collections.emptyList()); } /** * 這里返回的用戶密碼是否為庫里保存的密碼,是明文/密文,取決于認(rèn)證時密碼比對部分的實現(xiàn),每個人的場景不一樣, * 因為使用的是不加密的PasswordEncoder,所以可以返回明文 */ return new org.springframework.security.core.userdetails.User(username, user.getPassword(), Collections.emptyList()); } }
自定義認(rèn)證的bean配置
@Configuration public class WebConfiguration { @Bean public PasswordEncoder passwordEncoder() { // 示例,不對密碼進行加密處理 return NoOpPasswordEncoder.getInstance(); } @Bean public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); // 設(shè)置加載用戶信息的類 provider.setUserDetailsService(userDetailsService); // 比較用戶密碼的時候,密碼加密方式 provider.setPasswordEncoder(passwordEncoder); return new ProviderManager(Arrays.asList(provider)); } }
注意:
- 因為這個是示例,AuthenticationProvider使用的是spring security的DaoAuthenticationProvider
- 在實際場景中,如果不滿足可以自定義實現(xiàn)或者繼承DaoAuthenticationProvider
重寫其中的:
additionalAuthenticationChecks方法,主要就是認(rèn)證檢查的,默認(rèn)實現(xiàn)如下:
@Override @SuppressWarnings("deprecation") protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { this.logger.debug("Failed to authenticate since no credentials provided"); throw new BadCredentialsException(this.messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } String presentedPassword = authentication.getCredentials().toString(); // 就是比對下請求傳過來的密碼和根據(jù)該用戶查詢的密碼是否一致,passwordEncoder是根據(jù)不同的加密算法進行加密,示例我們用的是NoOpPasswordEncoder,也就是原始明文比對 if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { this.logger.debug("Failed to authenticate since password does not match stored value"); throw new BadCredentialsException(this.messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } }
定義登錄接口
@RequestMapping("/login") @RestController public class LoginController { private final AuthenticationManager authenticationManager; public LoginController(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @PostMapping() public Object login(@RequestBody User user) { try { // 使用定義的AuthenticationManager進行認(rèn)證處理 Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword())); // 認(rèn)證通過,設(shè)置到當(dāng)前上下文,如果當(dāng)前認(rèn)證過程后續(xù)還有處理的邏輯需要的話。這個示例是沒有必要了 SecurityContextHolder.getContext().setAuthentication(authenticate); return "login success"; }catch (Exception e) { return "login failed"; } } /** * 獲取驗證碼,需要的話,可以提供一個驗證碼獲取的接口,在上面的login里把驗證碼傳進來進行比對 */ @GetMapping("/captcha") public Object captcha() { return "1234"; } }
自定義HttpSecurity
@Component public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // 在這里自定義配置 http.authorizeRequests() // 登錄相關(guān)接口都允許訪問 .antMatchers("/login/**").permitAll() .anyRequest() .authenticated() .and() .exceptionHandling() // 認(rèn)證失敗返回401狀態(tài)碼,前端頁面可以根據(jù)401狀態(tài)碼跳轉(zhuǎn)到登錄頁面 .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase())) .and().cors() // csrf是否決定禁用,請自行考量 .and().csrf().disable() // 采用http 的基本認(rèn)證. .httpBasic(); } }
測試
示例中,用戶密碼寫死是:pass,
- 用一個錯誤的密碼試一下,響應(yīng)登錄失?。?/li>
- 使用正確的密碼,響應(yīng)登錄成功:
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
如何解決shardingsphere報錯Missing?the?data?source?name:‘null‘
使用ShardingSphere進行分庫操作時,如果遇到“Missing?the?datasource?name:?‘null’”的錯誤,通常是因為所操作的表沒有配置相關(guān)的路由信息,例如,如果在properties中僅配置了health_record和health_task的路由規(guī)則2024-11-11java網(wǎng)絡(luò)編程基礎(chǔ)知識介紹
這篇文章主要介紹了java網(wǎng)絡(luò)編程基礎(chǔ)知識介紹,涉及OSI分層模型和TCP/IP分層模型的對應(yīng)關(guān)系、IP地址、端口號、tcp、udp等相關(guān)內(nèi)容,還是比較不錯的,這里分享給大家,供需要的朋友參考。2017-11-11

Java基礎(chǔ)之查找文本特定內(nèi)容后進行修改