oauth 實現手機號碼登入

exception發表於2019-01-19

現在有一個需求就是改造 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… 來實現 本人已經驗證

相關文章