發送驗證碼的Controller
首先我們需要創建一個發送驗證碼的 Controller, 至於如何實現,這里就不多說了,大家都會的,本篇重點說明驗證部分.
【注意】在認證服務器上增加自己的 Controller, 默認情況下訪問是返回403,有兩種辦法解決:
- 把認證服務器也配制為資源服務器,既: 它既是認證服務器,也是資源服務器.並配制新增的Controller為任何人都能訪問
- 關閉認證服務器的 csrf.
由於我在認證服務器上增加的都是認證相關的功能,任何人都能訪問,不需要資源保護,所以我選擇了第二種方法.
關閉認證服務器的 csrf
在配制認證服務器的時候,我們創建過一個org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 的子類用於安全配制. 如果你當時沒有配制,增加一個就行.
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; 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.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); // 關閉 csrf } }
增加短信驗證碼的 TokenGranter
參考 org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter 的代碼
1 import org.apache.commons.lang3.StringUtils; 2 import org.springframework.core.env.Environment; 3 import org.springframework.security.authentication.AbstractAuthenticationToken; 4 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 import org.springframework.security.core.Authentication; 6 import org.springframework.security.core.userdetails.UserDetails; 7 import org.springframework.security.core.userdetails.UserDetailsChecker; 8 import org.springframework.security.core.userdetails.UserDetailsService; 9 import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; 10 import org.springframework.security.oauth2.provider.ClientDetails; 11 import org.springframework.security.oauth2.provider.ClientDetailsService; 12 import org.springframework.security.oauth2.provider.OAuth2Authentication; 13 import org.springframework.security.oauth2.provider.OAuth2Request; 14 import org.springframework.security.oauth2.provider.OAuth2RequestFactory; 15 import org.springframework.security.oauth2.provider.TokenRequest; 16 import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; 17 import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; 18 19 public class SMSCodeTokenGranter extends AbstractTokenGranter { 20 21 private static final String GRANT_TYPE = "sms_code"; 22 23 public SMSCodeLoginTokenGranter(AuthorizationServerTokenServices tokenServices, 24 ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { 25 super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); 26 } 27 28 @Override 29 protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, 30 TokenRequest tokenRequest) { 31 32 Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters()); 33 String userMobileNo = parameters.get("username"); //客戶端提交的用戶名 34 String smscode = parameters.get("smscode"); //客戶端提交的驗證碼 35 36 // 從庫里查用戶 37 UserDetails user = 從庫里查找用戶的代碼略; 38 if(user == null) { 39 throw new InvalidGrantException("用戶不存在"); 40 } 41 42 驗證用戶狀態(是否警用等),代碼略 43 44 // 驗證驗證碼 45 String smsCodeCached = 獲取服務中保存的用戶驗證碼,代碼略.一般我們是在生成好后放到緩存中 46 if(StringUtils.isBlank(smsCodeCached)) { 47 throw new InvalidGrantException("用戶沒有發送驗證碼"); 48 } 49 if(!smscode.equals(smsCodeCached)) { 50 throw new InvalidGrantException("驗證碼不正確"); 51 }else { 52 驗證通過后從緩存中移除驗證碼,代碼略 53 } 54 55 56 Authentication userAuth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); 57 // 關於user.getAuthorities(): 我們的自定義用戶實體是實現了 58 // org.springframework.security.core.userdetails.UserDetails 接口的, 所以有 user.getAuthorities() 59 // 當然該參數傳null也行 60 ((AbstractAuthenticationToken) userAuth).setDetails(parameters); 61 62 OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); 63 return new OAuth2Authentication(storedOAuth2Request, userAuth); 64 } 65 66 }
把 SMSCodeTokenGranter 加入到 CompositeTokenGranter 需要的 List 中
在上一篇中我們修改了 OAuth2AuthorizationServerConfig類,現在繼續修改:
我們在 getDefaultTokenGranters 方法中加入: getDefaultTokenGranters 的完整代碼:
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 ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); } tokenGranters.add(new SMSCodeLoginTokenGranter(tokenServices, clientDetails, requestFactory, userDetailsService)); return tokenGranters; }
其他代碼不用修改
如何使用我們新增的短信驗證碼方式?
我們調用 /oauth/token 進行認證的時候,有一個 grant_type 參數,我們把它的值改為 sms_code
password 參數可以不要了,新增一個 smscode 參數
當然,上面的 “sms_code” 和 “smscode” 也是可以修改的:
“sms_code” 對應 SMSCodeTokenGranter 中的靜態常量 GRANT_TYPE
“smscode” 對應 SMSCodeTokenGranter.getOAuth2Authentication 方法中的 parameters.get(“smscode”);