現在有一個需求就是改造 oauth2.0 實現手機號碼可以登入 需要重幾個類
第一個類
public class PhoneLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String SPRING_SECURITY_RESTFUL_PHONE_KEY = "phone";
private static final String SPRING_SECURITY_RESTFUL_VERIFY_CODE_KEY = "verifyCode";
private static final String SPRING_SECURITY_RESTFUL_LOGIN_URL = "/oauth/phoneLogin";
private boolean postOnly = true;
public PhoneLoginAuthenticationFilter() {
super(new AntPathRequestMatcher(SPRING_SECURITY_RESTFUL_LOGIN_URL, "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());
}
AbstractAuthenticationToken authRequest;
String principal;
String credentials;
// 手機驗證碼登陸
principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_PHONE_KEY);
credentials = obtainParameter(request, SPRING_SECURITY_RESTFUL_VERIFY_CODE_KEY);
principal = principal.trim();
authRequest = new PhoneAuthenticationToken(principal, credentials);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
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;
}
第二個類
public class PhoneAuthenticationProvider extends MyAbstractUserDetailsAuthenticationProvider {
private UserDetailsService userDetailsService;
@Override
protected void additionalAuthenticationChecks(UserDetails var1, Authentication authentication) throws AuthenticationException {
if(authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("PhoneAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
// 驗證碼驗證,呼叫公共服務查詢 key 為authentication.getPrincipal()的value, 並判斷其與驗證碼是否匹配
if(!"1000".equals(presentedPassword)){
this.logger.debug("Authentication failed: verifyCode does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("PhoneAuthenticationProvider.badCredentials", "Bad verifyCode"));
}
}
}
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
PhoneAuthenticationToken result = new PhoneAuthenticationToken(principal, authentication.getCredentials(), user.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
@Override
protected UserDetails retrieveUser(String phone, Authentication authentication) throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(phone);
} catch (UsernameNotFoundException var6) {
throw var6;
} catch (Exception var7) {
throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
}
if(loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
}
@Override
public boolean supports(Class<?> authentication) {
return PhoneAuthenticationToken.class.isAssignableFrom(authentication);
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
第三個類
@Service
public class PhoneUserDetailService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
System.out.println("PhoneUserDetailService");
return new User("admin", "1000", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
第四個類
public class PhoneAuthenticationToken extends MyAuthenticationToken {
public PhoneAuthenticationToken(Object principal, Object credentials) {
super(principal, credentials);
}
public PhoneAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
第五個類
@Component(“MyLoginAuthSuccessHandler”)
public class MyLoginAuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// String clientId = obtainParameter(request, “client_id”);
// String client_secret = obtainParameter(request, “client_secret”);
String header = request.getHeader("Authorization");
header.toLowerCase().startsWith("basic ");
String[] strings = extractAndDecodeHeader(header, request);
String clientId = strings[0];
String client_secret = strings[1];
String clientSecret = new BCryptPasswordEncoder().encode(client_secret);
System.out.println(clientSecret);
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (null == clientDetails) {
throw new UnapprovedClientAuthenticationException("clientId不存在" + clientId);
} else if (!new BCryptPasswordEncoder().matches(client_secret, clientDetails.getClientSecret())) {
throw new UnapprovedClientAuthenticationException("clientSecret不匹配" + clientId);
}
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "phone");
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
Set<String> scope = token.getScope();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(scope.stream().findFirst());
scope.stream().forEach(s -> {
stringBuffer.append("," + s);
});
Map<String, Object> map = new HashMap<>();
map.put("access_token", token.getValue());
map.put("token_type", token.getTokenType());
map.put("refresh_token", token.getRefreshToken().getValue());
map.put("expires_in", token.getExpiresIn());
map.put("scope", scope.stream().findFirst());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JsonUtil.toJsonString(map));
}
private String obtainParameter(HttpServletRequest request, String parameter) {
String result = request.getParameter(parameter);
return result == null ? "" : result;
}
private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
}
catch (IllegalArgumentException e) {
throw new BadCredentialsException(
"Failed to decode basic authentication token");
}
String token = new String(decoded, "UTF-8");
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
}
}
最後在 SecurityConfig 配置一下
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SsoUserDetailsService ssoUserDetailsService;
@Autowired
private PhoneUserDetailService phoneUserDetailService;
@Autowired
private QrUserDetailService qrUserDetailService;
@Autowired
private MyLoginAuthSuccessHandler myLoginAuthSuccessHandler;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/authentication/require", "/**/*.js",
"/**/*.css",
"/**/*.jpg",
"/**/*.png",
"/**/*.woff2",
"/oauth/exit",
"/oauth/logout"
);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(getPhoneLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(getQrLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.formLogin().loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")
.successHandler(myLoginAuthSuccessHandler).and()
.authorizeRequests().antMatchers("/authentication/require",
"/authentication/form",
"/**/*.js",
"/**/*.css",
"/**/*.jpg",
"/**/*.png",
"/**/*.woff2",
"/auth/*",
"/oauth/*",
)
.permitAll()
.anyRequest().authenticated().and().anonymous().disable().exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login?error")).and()
.csrf().disable();
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(phoneAuthenticationProvider());
auth.authenticationProvider(daoAuthenticationProvider());
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider1 = new DaoAuthenticationProvider();
// 設定userDetailsService
provider1.setUserDetailsService(ssoUserDetailsService);
// 禁止隱藏使用者未找到異常
provider1.setHideUserNotFoundExceptions(false);
// 使用BCrypt進行密碼的hash
provider1.setPasswordEncoder(passwordEncode());
return provider1;
}
@Bean
public PhoneAuthenticationProvider phoneAuthenticationProvider(){
PhoneAuthenticationProvider provider = new PhoneAuthenticationProvider();
// 設定userDetailsService
provider.setUserDetailsService(phoneUserDetailService);
// 禁止隱藏使用者未找到異常
provider.setHideUserNotFoundExceptions(false);
return provider;
}
@Override
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**
* 手機驗證碼登陸過濾器
* @return
*/
@Bean
public PhoneLoginAuthenticationFilter getPhoneLoginAuthenticationFilter() {
PhoneLoginAuthenticationFilter filter = new PhoneLoginAuthenticationFilter();
try {
filter.setAuthenticationManager(this.authenticationManagerBean());
} catch (Exception e) {
e.printStackTrace();
}
filter.setAuthenticationSuccessHandler(myLoginAuthSuccessHandler);
filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login?error"));
return filter;
}
}
配置好了
本文程式碼參考 https://github.com/fp2952/spr… 來實現 本人已經驗證