SpringSecurity認證流程:loadUserByUsername()方法內部實現。
實現步驟:
- 構建一個自定義的service接口,實現SpringSecurity的UserDetailService接口。
- 建一個service實現類,實現此loadUserByUsername方法。
- 調用登錄的login接口,會經過authenticationManager.authenticate(authenticationToken)方法。此方法會調用loadUserByUsername方法。
- 方法內部做用戶信息的查詢,判斷用戶名和密碼是否正確,這是第一道認證。
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {private final SysUserMapper sysUserMapper;//用戶登錄請求/login,自動調用方法//根據用戶名獲取用戶信息//UserDetails 存儲用戶信息,包括用戶名,密碼,權限@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();wrapper.eq(SysUser::getUsername, username);SysUser sysUser = sysUserMapper.selectOne(wrapper);if (Objects.isNull(sysUser)){throw new UsernameNotFoundException("用戶名不存在");}//認證成功回UserDetails對象return new LoginUser(sysUser);}}
@ToString
public class LoginUser implements UserDetails {private SysUser sysUser;public LoginUser(SysUser sysUser) {this.sysUser = sysUser;}// 權限@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return List.of();}@Overridepublic String getPassword() {return sysUser.getPassword();}@Overridepublic String getUsername() {return sysUser.getUsername();}// 賬號是否過期@Overridepublic boolean isAccountNonExpired() {return true;}// 賬號是否被鎖定@Overridepublic boolean isAccountNonLocked() {return true;}// 密碼是否過期@Overridepublic boolean isCredentialsNonExpired() {return true;}// 賬號是否可用@Overridepublic boolean isEnabled() {return true;}
}
5.如果沒有查到信息就拋出異常。
6.如果查到信息了再接著查用戶的權限信息,返回權限信息到loginUser實體。
7.此實體實現了SpringSecurity自帶的userDetail接口。實現了getAuthorities方法。
.8每次查詢權限都會調用此方法。
9.查詢到的權限,會被返回到login接口。進行后續操作。
10.如果認證通過,通過身份信息中的userid生產一個jwt。
11.把完整的用戶信息作為value,token作為key存入redis。
@RestController
@Tag(name = "認證模塊", description = "認證模塊")
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {//注入AuthenticationManager(認證管理器)private final AuthenticationManager authenticationManager;private final JwtUtils jwtUtils;@PostMapping("/login")@Operation(summary = "登錄")public Result login(@RequestParam("username") String username, @RequestParam("password") String password) {System.out.println(username + password);//登錄邏輯//調用UserDetailsService.loadUserByUsername方法獲取//不能直接調用,需要通過AuthenticationManager進行認證Authentication authentication = new UsernamePasswordAuthenticationToken(username, password);Authentication authenticate = null;try {authenticate = authenticationManager.authenticate(authentication);} catch (BadCredentialsException e) {return Result.failed(ResultCode.USERNAME_OR_PASSWORD_ERROR);}//認證成功方法tokenString token=jwtUtils.generateToken(authenticate);return Result.success(token);}
}
@Configuration
@EnableWebSecurity // 開啟web安全
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {private final IgnoredUrl ignoredUrl;/*** 配置認證管理器 AuthenticationManager* 作用:用于身份認證* 參數:UserDetailsService, PasswordEncoder*/@Beanpublic AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();provider.setUserDetailsService(userDetailsService);provider.setPasswordEncoder(passwordEncoder);return new ProviderManager(provider);}/*** 密碼編碼器** @return*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {//關閉csrf防護,否則回導致登錄失敗http.csrf(a -> a.disable()); //禁用SCRF//配置安全攔截規則http.authorizeHttpRequests(req ->req.requestMatchers(ignoredUrl.getUrls()).permitAll().anyRequest().authenticated());/*** 配置登錄頁*/http.formLogin(form -> form.loginPage("/").successForwardUrl("/index") //登錄成功跳轉頁面.loginProcessingUrl("/login")//登錄處理url.failureForwardUrl("/error") //登錄失敗跳轉頁面// .usernameParameter("name")//自定義用戶名參數// .passwordParameter("password")//自定義密碼參數);return http.build();}
}
@Component
public class JwtUtils {@Value("${jwt.secret}")private String secret;@Value("${jwt.expiration}")private Integer expiration;/****/public String generateToken(Authentication authentication) {Date now = new Date();Date expirationDate = DateUtil.offsetSecond(now, expiration);Map<String, Object> claims = new HashMap<>();claims.put("username", authentication.getName());//用戶名claims.put("exp", expirationDate);// claims.put();return JWTUtil.createToken(claims, secret.getBytes());}
}
@Component
@Data
@ConfigurationProperties(prefix = "security.ignored")
@ToString
public class IgnoredUrl {private String[] urls;
}
登錄成功
登陸失敗