【spring security】認證授權實現
基本實現
1、實現UserDetails介面
這個介面是spring security提供的核心使用者資訊。實現這個介面去定義使用者的
username、password
他使用者資訊(暱稱,手機號......)
許可權資訊
使用者的管理資訊(是否啟用、是否過期)
public class UserDTO implements UserDetails { private String username; private String password; private String mobile; private List<RoleDTO> roleDTOS; // 獲取使用者許可權 @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> list = new ArrayList<>(); for (RoleDTO roleDTO : roleDTOS) { SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(roleDTO.getRoleName()); list.add(simpleGrantedAuthority); } return list; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } public String getMobile() { return this.mobile; } public List<RoleDTO> getRoleDTOS() { return this.roleDTOS; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setMobile(String mobile) { this.mobile = mobile; } public void setRoleDTOS(List<RoleDTO> roleDTOS) { this.roleDTOS = roleDTOS; } // 是否過期 @Override public boolean isAccountNonExpired() { return true; } // 是否鎖定 @Override public boolean isAccountNonLocked() { return true; } // 使用者密碼是否過期 @Override public boolean isCredentialsNonExpired() { return true; } // 使用者是否啟用 @Override public boolean isEnabled() { return true; } }
2、實現UserDetailsService介面
這個介面是載入使用者資料的核心介面。前面有說認證邏輯需要呼叫UserDetailsService的loadUserByUsername()方法查詢使用者資訊。
@Service public class UserServiceImpl implements UserDetailsService { @Autowired private BCryptPasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // TODO 根據username查庫,這裡先隨便new了一個使用者 UserDTO userDTO = new UserDTO(); userDTO.setUsername(username); userDTO.setMobile("987654"); // 密碼需要提供加密之後的密碼 userDTO.setPassword(passwordEncoder.encode("123456")); // 給使用者添加了兩個許可權 ROLE_SYS ROLE_USER List<RoleDTO> list = new ArrayList<>(); list.add(new RoleDTO("ROLE_SYS")); list.add(new RoleDTO("ROLE_USER")); userDTO.setRoleDTOS(list); return userDTO; } }
3、Security配置
繼承WebSecurityConfigurerAdapter類,對Security的安全策略、靜態資源等進行配置
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserServiceImpl userService; @Autowired private CustomAccessDeniedHandler accessDeniedHandler; @Autowired private CustomFailHandler failHandler; @Autowired private CustomSuccessHandler successHandler; /** * 認證相關配置,AuthenticationManagerBuilder是用於構建AuthenticationManager的。 * AuthenticationManager中存有處理各種認證操作的AuthenticationProvider */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(getBCryptPasswordEncoder()); } /** * 配置靜態資源 */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/static/**"); } /** * 配置安全策略 */ @Override protected void configure(HttpSecurity http) throws Exception { // 禁用csrf http.csrf().disable() // 請求配置 .authorizeRequests() // 配置請求許可權 hasRole hasAuthority 兩種方式差一個 ROLE_ 字首 .antMatchers("/sys/**").hasAuthority("ROLE_SYS") .antMatchers("/user/**").hasAuthority("ROLE_USER") .antMatchers("/car/**").hasAuthority("ROLE_CAR") // 配置介面不做任何控制 .antMatchers("/login/**").permitAll() // 其他介面都需要進行認證 .anyRequest().authenticated() .and().exceptionHandling() // 認證失敗的處理器 .accessDeniedHandler(accessDeniedHandler); // 登入相關配置 http.formLogin().loginProcessingUrl("/login") .usernameParameter("username").passwordParameter("password") .defaultSuccessUrl("/default", true) .failureHandler(failHandler) .successHandler(successHandler); // 登出相關配置 http.logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .deleteCookies("remember-me") .invalidateHttpSession(true); // 出校成功的處理器 // .logoutSuccessHandler(logoutSuccessHandler); // 記住我功能 http.rememberMe() .tokenValiditySeconds(3600); } @Bean BCryptPasswordEncoder getBCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); } }
這裡為 /sys/** /user/** /car/** 分別設定了 ROLE_SYS ROLE_USER ROLE_CAR 許可權。使用者擁有ROLE_SYS ROLE_USER 許可權,所以訪問/car/**時會走到認證失敗處理器。
這裡對於許可權有兩種概念 Authority 和 role。這兩個更像是同一個東西的兩個概念,role 比 Authority 多了一個 ROLE_ 字首,區分兩個可以在業務上做更好的區分。
public ExpressionInterceptUrlRegistry hasAuthority(String authority) {
return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
}
private static String hasAuthority(String authority) {
return "hasAuthority('" + authority + "')";
}
public ExpressionInterceptUrlRegistry hasRole(String role) {
return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
Assert.isTrue(!role.startsWith("ROLE_"),
() -> "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
return "hasRole('ROLE_" + role + "')";
}
在許可權對比中也都是呼叫了同一個方法做對比
@Override
public final boolean hasAuthority(String authority) {
return hasAnyAuthority(authority);
}
@Override
public final boolean hasAnyAuthority(String... authorities) {
return hasAnyAuthorityName(null, authorities);
}
@Override
public final boolean hasRole(String role) {
return hasAnyRole(role);
}
@Override
public final boolean hasAnyRole(String... roles) {
return hasAnyAuthorityName(this.defaultRolePrefix, roles);
}
private boolean hasAnyAuthorityName(String prefix, String... roles) {
Set<String> roleSet = getAuthoritySet();
for (String role : roles) {
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {
return true;
}
}
return false;
}
自定義登入方式
1、自定義過濾器
繼承AuthenticationProvider。定義攔截的請求路徑及請求方式,重寫attemptAuthentication()方法呼叫對應的認證邏輯處理器
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private String mobileParameter = "mobile";
/**
* 是否僅 POST 方式
*/
private boolean postOnly = true;
public CustomAuthenticationFilter() {
super(new AntPathRequestMatcher("/login/mobile", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String mobile = obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
CustomAuthenticationToken authRequest = new CustomAuthenticationToken(mobile);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(mobileParameter);
}
protected void setDetails(HttpServletRequest request, CustomAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public String getMobileParameter() {
return mobileParameter;
}
public void setMobileParameter(String mobileParameter) {
Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");
this.mobileParameter = mobileParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
}
2、自定義Token實體
繼承AbstractAuthenticationToken。作為過濾器尋找對應認證處理器的媒介
public class CustomAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
/**
* 構建一個沒有鑑權的 CustomAuthenticationToken
*/
public CustomAuthenticationToken(Object principal) {
super(null);
this.principal = principal;
setAuthenticated(false);
}
/**
* 構建擁有鑑權的 CustomAuthenticationToken
*/
public CustomAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
// must use super, as we override
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
3、自定義認證邏輯的處理器
實現AuthenticationProvider。定義authenticate()方法完成認證邏輯,定義supports()方法用於過濾器與對應處理器的匹配媒介
public class CustomAuthenticationProvider implements AuthenticationProvider {
private UserService userService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
CustomAuthenticationToken authenticationToken = (CustomAuthenticationToken) authentication;
String mobile = (String) authenticationToken.getPrincipal();
UserDetails userDetails = userService.loadUserByUsername(mobile);
// todo 校驗,驗證碼、使用者
// 校驗成功,構建擁有鑑權的CustomAuthenticationToken返回
CustomAuthenticationToken authenticationResult = new CustomAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
// 判斷 authentication 是不是 CustomAuthenticationToken 的子類或子介面,用於匹配過濾器和認證處理器
return CustomAuthenticationToken.class.isAssignableFrom(authentication);
}
public void setUserDetailsService(UserService userService) {
this.userService = userService;
}
}
4、修改配置
配置自定義的Provider與Filter
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
CustomAuthenticationProvider customAuthenticationProvider = new CustomAuthenticationProvider();
customAuthenticationProvider.setUserDetailsService(userService);
auth.authenticationProvider(customAuthenticationProvider);
auth.userDetailsService(userService).passwordEncoder(getBCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter();
customAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
customAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
customAuthenticationFilter.setAuthenticationFailureHandler(failHandler);
// 過濾器配置
http
.addFilterAfter(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
......
}
推薦文章:
Spring Security 中的 hasRole 和 hasAuthority 的區別https://cloud.tencent.com/developer/article/1703187