(一)、spring boot security 認證--自定義登入實現
阿新 • • 發佈:2019-01-08
簡介
spring security主要分為兩部分,認證(authentication)和授權(authority)。
這一篇主要是認證部分,它由 ProviderManager(AuthenticationManager)實現。具體層次結構如下:
認證的核心就是登入,這裡簡單介紹下security自定義token登入的實現邏輯,同時相容使用者名稱密碼登入。
大體分為以下幾個步驟:
- 自定義AuthenticationToken實現: 不同登入方式使用不同的token
- 自定義AuthenticationProcessingFilter實現:用來過濾指定的登入方式,生成對應的自定義AuthenticationToken實現
- 自定義AuthenticationProvider實現:針對不同登入方式提供的認證邏輯
- 自定義UserDetailsService實現:自定義使用者資訊查詢服務
- WebSecurityConfigurerAdapter宣告:security資訊配置,將前面的自定義物件注入到流程中。
程式碼路徑
步驟說明
注:僅說明實現方式,邏輯簡化處理。
1、自定義AuthenticationProcessingFilter實現
package demo.model; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; /** * * @Description: 宣告自定義token,是為後面的AuthenticationProvider提供支撐,區分不同型別的處理。 * * @auther: csp * @date: 2019/1/7 下午6:25 * */ public class LoginToken extends AbstractAuthenticationToken { private final String token; public LoginToken(String token) { super(null); this.token = token; } public LoginToken(String token, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.token = token; setAuthenticated(true); } // 這個地方傳遞下token,邏輯是簡化的邏輯,具體可以根據實際場景處理。 // 如jwt token,解析出來username等資訊,放到該token中。 @Override public Object getCredentials() { return this.token; } @Override public Object getPrincipal() { return null; } }
2、自定義AuthenticationProcessingFilter實現
package demo.filter; import demo.model.LoginToken; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * @Description: 自定義filter,用來篩選出來想要的登入方式。 * * @auther: csp * @date: 2019/1/7 下午6:27 * */ public class MyTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private static final String SPRING_SECURITY_RESTFUL_TOKEN = "token"; public static final String SPRING_SECURITY_RESTFUL_LOGIN_URL = "/tokenLogin"; private boolean postOnly = true; // 請求路徑宣告,url不能被許可權攔截。 // 會根據AntPathRequestMatcher 篩選請求,符合條件的才會認為有效 public MyTokenAuthenticationFilter() { super(new AntPathRequestMatcher(SPRING_SECURITY_RESTFUL_LOGIN_URL, null)); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { AbstractAuthenticationToken authRequest; String token = obtainParameter(request, SPRING_SECURITY_RESTFUL_TOKEN); authRequest = new LoginToken(token); // Allow subclasses to set the "details" property setDetails(request, authRequest); // 根據AuthenticationManager校驗具體的請求,實際的登入驗證觸發。 return this.getAuthenticationManager().authenticate(authRequest); } private void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } private String obtainParameter(HttpServletRequest request, String parameter) { String result = request.getParameter(parameter); return result == null ? "" : result; } }
3、自定義AuthenticationProvider實現
package demo.provider;
import demo.model.LoginToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
*
* @Description: token驗證邏輯
*
* @auther: csp
* @date: 2019/1/7 下午9:05
*
*/
public class MyTokenProvider implements AuthenticationProvider {
UserDetailsService userDetailsService;
public MyTokenProvider(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (authentication.getCredentials() == null) ? "NONE_PROVIDED"
: (String) authentication.getCredentials();
// loginToken_user
// 這個地方簡化處理,實際需要校驗token,如jwt token 需要解密 驗證資訊
if (token.startsWith("loginToken_")) {
// 驗證下token對不對,然後載入下資訊。
String userName = token.split("_")[1];
UserDetails user = userDetailsService.loadUserByUsername(userName);
LoginToken result = new LoginToken(token, user.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
throw new BadCredentialsException("token無效");
}
/**
*
* @Description: 只處理特定型別的登入
*
* @auther: csp
* @date: 2019/1/7 下午9:03
* @param authenticationClass
* @return: boolean
*
*/
@Override
public boolean supports(Class<?> authenticationClass) {
return (LoginToken.class
.isAssignableFrom(authenticationClass));
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
4、自定義UserDetailsService實現
package demo.service;
import demo.model.UrlGrantedAuthority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
*
* @Description: 使用者資訊查詢邏輯,這裡token認證和使用者名稱登入使用同一個service
*
* @auther: csp
* @date: 2019/1/7 下午9:06
*
*/
@Component public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("使用者的使用者名稱: {}", username);
List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
// 模擬下邏輯,簡單處理下。
if ("admin".equals(username)) {
// 自定義許可權實現
UrlGrantedAuthority authority = new UrlGrantedAuthority(null, "/admin/index");
list.add(authority);
// 封裝使用者資訊,並返回。引數分別是:使用者名稱,密碼,使用者許可權
User user = new User(username, "123456", list);
return user;
}
else if ("user".equals(username)) {
list.add(new SimpleGrantedAuthority("ROLE_USER"));
User user = new User(username, "123456", list);
return user;
}
else {
throw new DisabledException("使用者不存在");
}
}
}
5、WebSecurityConfigurerAdapter宣告
package demo.config;
import demo.filter.MyTokenAuthenticationFilter;
import demo.provider.MyTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService myUserDetailsService;
// @formatter:off
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 將tokenfilter追加進去,篩選出來tokenLogin邏輯。
.addFilterBefore(getTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.logout().logoutUrl("/logout").logoutSuccessUrl("/").and()
.formLogin().loginPage("/login").defaultSuccessUrl("/").failureUrl("/login-error").permitAll().and()
.authorizeRequests()
.antMatchers(MyTokenAuthenticationFilter.SPRING_SECURITY_RESTFUL_LOGIN_URL).permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated();
}
// @formatter:on
@Override
public void configure(WebSecurity web) throws Exception {
//忽略請求 不走security filters
web.ignoring().antMatchers("/login-error2","/css/**","/info","/health","/hystrix.stream");
}
/**
* 1、使用者驗證,指定多個AuthenticationProvider
* 實際執行時候根據provider的supports方法判斷是否走邏輯
*
* 2、如果不覆蓋,優先會獲取AuthenticationProvider bean作為provider;
* 如果沒有bean,預設提供DaoAuthenticationProvider
*
* @param auth
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myTokenProvider());
// 未配置時候使用者名稱密碼預設登入provider
auth.authenticationProvider(daoAuthenticationProvider());
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider1 = new DaoAuthenticationProvider();
// 設定userDetailsService
provider1.setUserDetailsService(myUserDetailsService);
// 禁止隱藏使用者未找到異常
provider1.setHideUserNotFoundExceptions(false);
// 使用BCrypt進行密碼的hash
// provider1.setPasswordEncoder(myEncoder());
return provider1;
}
/**
*
* @Description: 自定義token方式認證邏輯provider
*
* @auther: csp
* @date: 2019/1/7 下午9:18
* @return: demo.provider.MyTokenProvider
*
*/
@Bean
public MyTokenProvider myTokenProvider() {
return new MyTokenProvider(myUserDetailsService);
}
// @Bean
public BCryptPasswordEncoder myEncoder(){
return new BCryptPasswordEncoder(6);
}
/**
* token登入過濾器,用來篩選出來token登入方式。
*/
@Bean
public MyTokenAuthenticationFilter getTokenAuthenticationFilter() {
MyTokenAuthenticationFilter filter = new MyTokenAuthenticationFilter();
try {
// 使用的是預設的authenticationManager
filter.setAuthenticationManager(this.authenticationManagerBean());
} catch (Exception e) {
e.printStackTrace();
}
// filter.setAuthenticationSuccessHandler(new MyLoginAuthSuccessHandler());
filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/"));
filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login-error2"));
return filter;
}
}
6、驗證
- 使用者名稱密碼登入:
admin 123456
user 123456
- token登入:
user登入:
http://127.0.0.1:9999/tokenLogin?token=loginToken_user
admin登入:
http://127.0.0.1:9999/tokenLogin?token=loginToken_admin