> 文章列表 > SpringSecurity+OAUTH2集成多种登录方式

SpringSecurity+OAUTH2集成多种登录方式

SpringSecurity+OAUTH2集成多种登录方式

一、目前OAUTH2的提供了四种授权类型

Authorization Code(授权码模式):授权码模式, 通过授权码获取token进行资源访问。
Implicit(简化模式):用于移动应用程序或 Web 应用程序,这种模式比授权码模式少了code环节,回调url直接附带token。
Resource Owner Password Credentials(密码模式):资源所有者和客户端之间具有高度信任时(例如,客户端是设备的操作系统的一部分,或者是一个高度特权应用程序, 比如APP, 自研终端等),因为client可能存储用户密码。
Client Credentials(客户端模式):该模式直接根据client端的id和密钥即可获取token, 不需要用户参与, 适合内部的API应用服务使用。

但是在应用开发过程中,还存在多种登录方式,比如手机验证码、二维码扫码、微信登录等,如何在OAUTH2中集成其他多种登录方式。我们拿手机验证码登录模式举例,其他类型登录模式可以按照这种方式改造。

二、WEB端手机验证码登录

首先创建一个filter命名PhoneNumberAuthenticationFilter,该filter继承AbstractAuthenticationProcessingFilter类,oauth2自带的授权模式都是继承的该类,可以仿照password授权模式的过滤器UsernamePasswordAuthenticationFilter修改,修改请求地址为/phone/login,修改自己的参数。

public class PhoneNumberAuthenticationFilter extendsAbstractAuthenticationProcessingFilter {public static final String SPRING_SECURITY_FORM_PHONE_KEY = "phone";public static final String SPRING_SECURITY_FORM_CODE_KEY = "code";private String phoneParameter = SPRING_SECURITY_FORM_PHONE_KEY;private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;private boolean postOnly = true;public PhoneNumberAuthenticationFilter() {super(new AntPathRequestMatcher("/phone/login", "POST"));}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 phone = obtainPhone(request);if (phone == null) {phone = "";}phone = phone.trim();String code = obtainCode(request);PhoneNumAuthenticationToken authRequest = new PhoneNumAuthenticationToken(phone, code);setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}protected String obtainPhone(HttpServletRequest request) {return request.getParameter(phoneParameter);}protected String obtainCode(HttpServletRequest request) {return request.getParameter(codeParameter);}protected void setDetails(HttpServletRequest request,PhoneNumAuthenticationToken authRequest) {authRequest.setDetails(authenticationDetailsSource.buildDetails(request));}}

创建自己的token类PhoneNumAuthenticationToken。

public class PhoneNumAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = 420L;private final Object phone;private Object code;public PhoneNumAuthenticationToken(Object phone, Object code) {super(null);this.phone = phone;this.code = code;this.setAuthenticated(false);}public PhoneNumAuthenticationToken(Object phone, Object code, Collection<? extends GrantedAuthority> authorities) {super(authorities);this.phone = phone;this.code = code;super.setAuthenticated(true);}public Object getCredentials() {return this.code;}public Object getPrincipal() {return this.phone;}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");} else {super.setAuthenticated(false);}}public void eraseCredentials() {super.eraseCredentials();this.code = null;}
}

filter中要执行

this.getAuthenticationManager().authenticate(authRequest);

所以还要新增一个provider类PhoneAuthenticationProvider。

@Component
public class PhoneAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate IDaaSUserDetailsManager iDaaSUserDetailsManager;@Autowiredprivate ValidateCodeRepository validateCodeRepository;/* 手机号、验证码的认证逻辑* @param authentication 其实就是我们封装的 PhoneNumAuthenticationToken* @return* @throws AuthenticationException*/@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {PhoneNumAuthenticationToken token = (PhoneNumAuthenticationToken) authentication;String phone = (String) authentication.getPrincipal();// 获取手机号String code = (String) authentication.getCredentials(); // 获取输入的验证码//从数据库获取验证码ValidateCode validateCode = validateCodeRepository.queryLastCodeByPhoneType(phone,"1");if(null == validateCode){throw new BadCredentialsException("不存在短信发送记录,请重新发送");}String phoneNum = validateCode.getCode();if (!phoneNum.equals(code)) {throw new PhoneNumBadCredentialsException("验证码不正确");}if(DateUtils.differenceSecond(new Date(),validateCode.getValidateTime())>0){throw new PhoneNumBadCredentialsException("该验证码已经失效,请重新发送,验证码有效期10分钟");}if(validateCode.getIfUse().equals("1")){throw new PhoneNumBadCredentialsException("该验证码已经使用,请重新发送");}// 2. 根据手机号查询用户信息UserDetails userDetails = (UserDetails) iDaaSUserDetailsManager.loadUserByUsername(phone);if (userDetails == null) {throw new PhoneNumBadCredentialsException("用户不存在,请联系管理员开通");}// 3. 把用户封装到 PhoneNumAuthenticationToken 中,// 后面就可以使用 SecurityContextHolder.getContext().getAuthentication(); 获取当前登陆用户信息PhoneNumAuthenticationToken authenticationResult = new PhoneNumAuthenticationToken(userDetails, code, userDetails.getAuthorities());authenticationResult.setDetails(token.getDetails());return authenticationResult;}/* 判断是上面 authenticate 方法的 authentication 参数,是哪种类型* Authentication 是个接口,实现类有很多,目前我们最熟悉的就是 PhoneNumAuthenticationToken、UsernamePasswordAuthenticationToken* 很明显,我们只支持 PhoneNumAuthenticationToken,因为它封装的是手机号、验证码* @param authentication* @return*/@Overridepublic boolean supports(Class<?> authentication) {// 如果参数是 PhoneNumAuthenticationToken 类型,返回truereturn (PhoneNumAuthenticationToken.class.isAssignableFrom(authentication));}
}

我们只需要做一个html页面,创建一个form请求,url为:/phone/login,参数用phone和code进行请求即可返回token。

三、移动端手机验证码登录

首先在oauth_client_details表authorized_grant_types增加新的授权方式,比如sms.

然后找到源码里面的AuthorizationServerEndpointsConfigurer类,在自己工程的相同目录下面创建相同类名的AuthorizationServerEndpointsConfigurer,并修改getDefaultTokenGranters方法下面添加自己的授权模式SmsTokenGranter。

public final class AuthorizationServerEndpointsConfigurer {private AuthorizationServerTokenServices tokenServices;private ConsumerTokenServices consumerTokenServices;private AuthorizationCodeServices authorizationCodeServices;private ResourceServerTokenServices resourceTokenServices;private TokenStore tokenStore;private TokenEnhancer tokenEnhancer;private AccessTokenConverter accessTokenConverter;private ApprovalStore approvalStore;private TokenGranter tokenGranter;private OAuth2RequestFactory requestFactory;private OAuth2RequestValidator requestValidator;private UserApprovalHandler userApprovalHandler;private AuthenticationManager authenticationManager;private ClientDetailsService clientDetailsService;private String prefix;private Map<String, String> patternMap = new HashMap<String, String>();private Set<HttpMethod> allowedTokenEndpointRequestMethods = new HashSet<HttpMethod>();private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping;private boolean approvalStoreDisabled;private List<Object> interceptors = new ArrayList<Object>();private DefaultTokenServices defaultTokenServices;private UserDetailsService userDetailsService;private boolean tokenServicesOverride = false;private boolean userDetailsServiceOverride = false;private boolean reuseRefreshToken = true;private WebResponseExceptionTranslator exceptionTranslator;public AuthorizationServerTokenServices getTokenServices() {return ProxyCreator.getProxy(AuthorizationServerTokenServices.class,new ObjectFactory<AuthorizationServerTokenServices>() {@Overridepublic AuthorizationServerTokenServices getObject() throws BeansException {return tokenServices();}});}public TokenStore getTokenStore() {return tokenStore();}public TokenEnhancer getTokenEnhancer() {return tokenEnhancer;}public AccessTokenConverter getAccessTokenConverter() {return accessTokenConverter();}public ApprovalStore getApprovalStore() {return approvalStore;}public ClientDetailsService getClientDetailsService() {return ProxyCreator.getProxy(ClientDetailsService.class, new ObjectFactory<ClientDetailsService>() {@Overridepublic ClientDetailsService getObject() throws BeansException {return clientDetailsService();}});}public OAuth2RequestFactory getOAuth2RequestFactory() {return ProxyCreator.getProxy(OAuth2RequestFactory.class, new ObjectFactory<OAuth2RequestFactory>() {@Overridepublic OAuth2RequestFactory getObject() throws BeansException {return requestFactory();}});}public OAuth2RequestValidator getOAuth2RequestValidator() {return requestValidator();}public UserApprovalHandler getUserApprovalHandler() {return userApprovalHandler();}public AuthorizationServerEndpointsConfigurer tokenStore(TokenStore tokenStore) {this.tokenStore = tokenStore;return this;}public AuthorizationServerEndpointsConfigurer tokenEnhancer(TokenEnhancer tokenEnhancer) {this.tokenEnhancer = tokenEnhancer;return this;}public AuthorizationServerEndpointsConfigurer reuseRefreshTokens(boolean reuseRefreshToken) {this.reuseRefreshToken = reuseRefreshToken;return this;}public AuthorizationServerEndpointsConfigurer accessTokenConverter(AccessTokenConverter accessTokenConverter) {this.accessTokenConverter = accessTokenConverter;return this;}public AuthorizationServerEndpointsConfigurer tokenServices(AuthorizationServerTokenServices tokenServices) {this.tokenServices = tokenServices;if (tokenServices != null) {this.tokenServicesOverride = true;}return this;}public boolean isTokenServicesOverride() {return tokenServicesOverride;}public boolean isUserDetailsServiceOverride() {return userDetailsServiceOverride;}public AuthorizationServerEndpointsConfigurer userApprovalHandler(UserApprovalHandler approvalHandler) {this.userApprovalHandler = approvalHandler;return this;}public AuthorizationServerEndpointsConfigurer approvalStore(ApprovalStore approvalStore) {if (approvalStoreDisabled) {throw new IllegalStateException("ApprovalStore was disabled");}this.approvalStore = approvalStore;return this;}/* Explicitly disable the approval store, even if one would normally be added automatically (usually when JWT is not* used). Without an approval store the user can only be asked to approve or deny a grant without any more granular* decisions. @return this for fluent builder*/public AuthorizationServerEndpointsConfigurer approvalStoreDisabled() {this.approvalStoreDisabled = true;return this;}public AuthorizationServerEndpointsConfigurer prefix(String prefix) {this.prefix = prefix;return this;}public AuthorizationServerEndpointsConfigurer pathMapping(String defaultPath, String customPath) {this.patternMap.put(defaultPath, customPath);return this;}public AuthorizationServerEndpointsConfigurer addInterceptor(HandlerInterceptor interceptor) {this.interceptors.add(interceptor);return this;}public AuthorizationServerEndpointsConfigurer addInterceptor(WebRequestInterceptor interceptor) {this.interceptors.add(interceptor);return this;}public AuthorizationServerEndpointsConfigurer exceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) {this.exceptionTranslator = exceptionTranslator;return this;}/* The AuthenticationManager for the password grant. @param authenticationManager an AuthenticationManager, fully initialized* @return this for a fluent style*/public AuthorizationServerEndpointsConfigurer authenticationManager(AuthenticationManager authenticationManager) {this.authenticationManager = authenticationManager;return this;}public AuthorizationServerEndpointsConfigurer tokenGranter(TokenGranter tokenGranter) {this.tokenGranter = tokenGranter;return this;}/* N.B. this method is not part of the public API. To set up a custom ClientDetailsService please use* {@link AuthorizationServerConfigurerAdapter#configure(ClientDetailsServiceConfigurer)}.*/public void setClientDetailsService(ClientDetailsService clientDetailsService) {this.clientDetailsService = clientDetailsService;}public AuthorizationServerEndpointsConfigurer requestFactory(OAuth2RequestFactory requestFactory) {this.requestFactory = requestFactory;return this;}public AuthorizationServerEndpointsConfigurer requestValidator(OAuth2RequestValidator requestValidator) {this.requestValidator = requestValidator;return this;}public AuthorizationServerEndpointsConfigurer authorizationCodeServices(AuthorizationCodeServices authorizationCodeServices) {this.authorizationCodeServices = authorizationCodeServices;return this;}public AuthorizationServerEndpointsConfigurer allowedTokenEndpointRequestMethods(HttpMethod... requestMethods) {Collections.addAll(allowedTokenEndpointRequestMethods, requestMethods);return this;}public AuthorizationServerEndpointsConfigurer userDetailsService(UserDetailsService userDetailsService) {if (userDetailsService != null) {this.userDetailsService = userDetailsService;this.userDetailsServiceOverride = true;}return this;}public ConsumerTokenServices getConsumerTokenServices() {return consumerTokenServices();}public ResourceServerTokenServices getResourceServerTokenServices() {return resourceTokenServices();}public AuthorizationCodeServices getAuthorizationCodeServices() {return authorizationCodeServices();}public Set<HttpMethod> getAllowedTokenEndpointRequestMethods() {return allowedTokenEndpointRequestMethods();}public OAuth2RequestValidator getRequestValidator() {return requestValidator();}public TokenGranter getTokenGranter() {return tokenGranter();}public FrameworkEndpointHandlerMapping getFrameworkEndpointHandlerMapping() {return frameworkEndpointHandlerMapping();}public WebResponseExceptionTranslator getExceptionTranslator() {return exceptionTranslator();}private ResourceServerTokenServices resourceTokenServices() {if (resourceTokenServices == null) {if (tokenServices instanceof ResourceServerTokenServices) {return (ResourceServerTokenServices) tokenServices;}resourceTokenServices = createDefaultTokenServices();}return resourceTokenServices;}private Set<HttpMethod> allowedTokenEndpointRequestMethods() {// HTTP POST should be the only allowed endpoint request method by default.if (allowedTokenEndpointRequestMethods.isEmpty()) {allowedTokenEndpointRequestMethods.add(HttpMethod.POST);}return allowedTokenEndpointRequestMethods;}private ConsumerTokenServices consumerTokenServices() {if (consumerTokenServices == null) {if (tokenServices instanceof ConsumerTokenServices) {return (ConsumerTokenServices) tokenServices;}consumerTokenServices = createDefaultTokenServices();}return consumerTokenServices;}private AuthorizationServerTokenServices tokenServices() {if (tokenServices != null) {return tokenServices;}this.tokenServices = createDefaultTokenServices();return tokenServices;}public AuthorizationServerTokenServices getDefaultAuthorizationServerTokenServices() {if (defaultTokenServices != null) {return defaultTokenServices;}this.defaultTokenServices = createDefaultTokenServices();return this.defaultTokenServices;}private DefaultTokenServices createDefaultTokenServices() {DefaultTokenServices tokenServices = new DefaultTokenServices();tokenServices.setTokenStore(tokenStore());tokenServices.setSupportRefreshToken(true);tokenServices.setReuseRefreshToken(reuseRefreshToken);tokenServices.setClientDetailsService(clientDetailsService());tokenServices.setTokenEnhancer(tokenEnhancer());addUserDetailsService(tokenServices, this.userDetailsService);return tokenServices;}private TokenEnhancer tokenEnhancer() {if (this.tokenEnhancer == null && accessTokenConverter() instanceof JwtAccessTokenConverter) {tokenEnhancer = (TokenEnhancer) accessTokenConverter;}return this.tokenEnhancer;}private AccessTokenConverter accessTokenConverter() {if (this.accessTokenConverter == null) {accessTokenConverter = new DefaultAccessTokenConverter();}return this.accessTokenConverter;}private TokenStore tokenStore() {if (tokenStore == null) {if (accessTokenConverter() instanceof JwtAccessTokenConverter) {this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());}else {this.tokenStore = new InMemoryTokenStore();}}return this.tokenStore;}private ApprovalStore approvalStore() {if (approvalStore == null && tokenStore() != null && !isApprovalStoreDisabled()) {TokenApprovalStore tokenApprovalStore = new TokenApprovalStore();tokenApprovalStore.setTokenStore(tokenStore());this.approvalStore = tokenApprovalStore;}return this.approvalStore;}private boolean isApprovalStoreDisabled() {return approvalStoreDisabled || (tokenStore() instanceof JwtTokenStore);}private ClientDetailsService clientDetailsService() {if (clientDetailsService == null) {this.clientDetailsService = new InMemoryClientDetailsService();}if (this.defaultTokenServices != null) {addUserDetailsService(defaultTokenServices, userDetailsService);}return this.clientDetailsService;}private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {if (userDetailsService != null) {PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService));tokenServices.setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider> asList(provider)));}}private UserApprovalHandler userApprovalHandler() {if (userApprovalHandler == null) {if (approvalStore() != null) {ApprovalStoreUserApprovalHandler handler = new ApprovalStoreUserApprovalHandler();handler.setApprovalStore(approvalStore());handler.setRequestFactory(requestFactory());handler.setClientDetailsService(clientDetailsService);this.userApprovalHandler = handler;}else if (tokenStore() != null) {TokenStoreUserApprovalHandler userApprovalHandler = new TokenStoreUserApprovalHandler();userApprovalHandler.setTokenStore(tokenStore());userApprovalHandler.setClientDetailsService(clientDetailsService());userApprovalHandler.setRequestFactory(requestFactory());this.userApprovalHandler = userApprovalHandler;}else {throw new IllegalStateException("Either a TokenStore or an ApprovalStore must be provided");}}return this.userApprovalHandler;}private AuthorizationCodeServices authorizationCodeServices() {if (authorizationCodeServices == null) {authorizationCodeServices = new InMemoryAuthorizationCodeServices();}return authorizationCodeServices;}private WebResponseExceptionTranslator exceptionTranslator() {if (exceptionTranslator != null) {return exceptionTranslator;}exceptionTranslator = new DefaultWebResponseExceptionTranslator();return exceptionTranslator;}private OAuth2RequestFactory requestFactory() {if (requestFactory != null) {return requestFactory;}requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService());return requestFactory;}private OAuth2RequestValidator requestValidator() {if (requestValidator != null) {return requestValidator;}requestValidator = new DefaultOAuth2RequestValidator();return requestValidator;}private List<TokenGranter> getDefaultTokenGranters() {ClientDetailsService clientDetails = clientDetailsService();AuthorizationServerTokenServices tokenServices = tokenServices();AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();OAuth2RequestFactory requestFactory = requestFactory();List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,requestFactory));tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);tokenGranters.add(implicit);tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));if (authenticationManager != null) {tokenGranters.add(new SmsTokenGranter(authenticationManager, tokenServices,clientDetails, requestFactory));tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,clientDetails, requestFactory));}return tokenGranters;}private TokenGranter tokenGranter() {if (tokenGranter == null) {tokenGranter = new TokenGranter() {private CompositeTokenGranter delegate;@Overridepublic OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {if (delegate == null) {delegate = new CompositeTokenGranter(getDefaultTokenGranters());}return delegate.grant(grantType, tokenRequest);}};}return tokenGranter;}private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping() {if (frameworkEndpointHandlerMapping == null) {frameworkEndpointHandlerMapping = new FrameworkEndpointHandlerMapping();frameworkEndpointHandlerMapping.setMappings(patternMap);frameworkEndpointHandlerMapping.setPrefix(prefix);frameworkEndpointHandlerMapping.setInterceptors(interceptors.toArray());}return frameworkEndpointHandlerMapping;}}

创建手机验证码的granter类SmsTokenGranter

public class SmsTokenGranter extends AbstractTokenGranter {private static final String GRANT_TYPE = "sms";private final AuthenticationManager authenticationManager;public SmsTokenGranter(AuthenticationManager authenticationManager,AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);}protected SmsTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {super(tokenServices, clientDetailsService, requestFactory, grantType);this.authenticationManager = authenticationManager;}@Overrideprotected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
//        validateCodeApplication.checkCode(parameters);String username = parameters.get("phone");String password = parameters.get("code");// Protect from downstream leaks of passwordparameters.remove("password");Authentication userAuth = new PhoneNumAuthenticationToken(username, password);((AbstractAuthenticationToken) userAuth).setDetails(parameters);try {userAuth = authenticationManager.authenticate(userAuth);}catch (AccountStatusException ase) {//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)throw new InvalidGrantException(ase.getMessage());}catch (BadCredentialsException e) {// If the username/password are wrong the spec says we should send 400/invalid grantthrow new InvalidGrantException(e.getMessage());}catch (PhoneNumBadCredentialsException e){throw new InvalidGrantException(e.getMessage());}if (userAuth == null || !userAuth.isAuthenticated()) {throw new InvalidGrantException("Could not authenticate user: " + username);}OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);return new OAuth2Authentication(storedOAuth2Request, userAuth);}
}

在自己工程的AuthorizationServerConfigurerAdapter的实现类里面添加该granter

    @Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsManager).authorizationCodeServices(authorizationCodeServices).tokenStore(tokenStore).accessTokenConverter(jwtAccessTokenConverter).reuseRefreshTokens(false);List<TokenGranter> granters = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));/// 添加手机验证码的授权模式granters.add(new SmsTokenGranter(authenticationManager,endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));/// 这是一个组装模式,实现了TokenGranter接口,循环调用List中的TokenGranter组件进行校验处理,直到返回验证成功信息或者是异常信息CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granters);endpoints.tokenGranter(compositeTokenGranter);}

在调用/oauth/token接口获取token时,将grant_type改为sms,增加phone和code参数即可返回token.