springSecurity + OAuth2 獲取Token流程分析以及增加協議授權模式
一、什么是OAuth2協議?
OAuth 2.0 是一個關於授權的開放的網絡協議,是目前最流行的授權機制。
數據的所有者告訴系統,同意授權第三方應用進入系統,獲取這些數據。系統從而產生一個短期的進入令牌(token),用來代替密碼,供第三方應用使用。
由於授權的場景眾多,OAuth 2.0 協議定義了獲取令牌的四種授權方式,分別是:
- 授權碼模式:授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的后台服務器,與"服務提供商"的認證服務器進行互動。
- 簡化模式:簡化模式(implicit grant type)不通過第三方應用程序的服務器,直接在瀏覽器中向認證服務器申請令牌,跳過了"授權碼"這個步驟,因此得名。所有步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不需要認證。
- 密碼模式:密碼模式(Resource Owner Password Credentials Grant)中,用戶向客戶端提供自己的用戶名和密碼。客戶端使用這些信息,向"服務商提供商"索要授權。
- 客戶端模式:客戶端模式(Client Credentials Grant)指客戶端以自己的名義,而不是以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式並不屬於OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求"服務提供商"提供服務,其實不存在授權問題。
四種授權模式分別使用不同的
grant_type來區分
二、為什么要自定義授權類型?
雖然 OAuth2 協議定義了4種標准的授權模式,但是在實際開發過程中還是遠遠滿足不了各種變態的業務場景,需要我們去擴展。
例如增加圖形驗證碼、手機驗證碼、手機號密碼登錄等等的場景
而常見的做法都是通過增加 過濾器Filter 的方式來擴展 Spring Security 授權,但是這樣的實現方式有兩個問題:
- 脫離了
OAuth2的管理 - 不靈活:例如系統使用 密碼模式 授權,網頁版需要增加圖形驗證碼校驗,但是手機端APP又不需要的情況下,使用增加
Filter的方式去實現就比較麻煩了。
所以目前在 Spring Security 中比較優雅和靈活的擴展方式就是通過自定義 grant_type 來增加授權模式。
三、實現思路
在擴展之前首先需要先了解 Spring Security 的整個授權流程,我以 密碼模式 為例去展開分析,如下圖所示

3.1. 流程分析
整個授權流程關鍵點分為以下兩個部分:
我們先看一下獲取token的運行流程:
(1)在發起 URL+/oauth/token 獲取token的請求后,實際上是請求 TokenEndpoint 類的postAccessToken或者getAccessToken方法,就相當於一個普通的concoller請求方法,根據請求類型是get或者post,其實get請求內部也是調用post請求的方法)

(2)在postAccessToken這個方法中,在這個方法的132行調用TokenGranter類的grant方法來獲取token,這個方法也是最重要的,通過這個方法我們可以對請求的參數進行校驗是否合法,是否給予令牌。

(3)TokenGranter是一個接口,它有多個實現類,CompositeTokenGranter是其中之一,在grant方法中,會循環遍歷所有的授權方式,根據請求參數攜帶的授權方式碼,來匹配對應的授權處理實現類,調用實現類中的grant方法。那么關鍵點來了,請求參數中攜帶的是我們自定義的授權方式碼,如果要匹配上,那么首先我們要創建自定義的授權處理類,然后把這個授權處理類放入Spring Security默認的授權處理集合中,這樣才能循環匹配上,進行下一步。

4)創建自定義授權處理類,我們可以繼承TokenGranter來實現自定義的身份驗證以便獲取token,而AbstractTokenGranter是一個繼承TokenGranter的實現類,一般我們都會繼承這個類進行使用。
從下面代碼可以看出,這個抽象類的grant方法返回token,在最后調用了getOAuth2Authentication方法,所以
我們只需要繼承AbstractTokenGranter類然后重寫getOAuth2Authentication方法就可以了。

(5)對於參數的校驗,在上圖中36行代碼,authenticationManager.authenticate(userAuth);這個方法中進行校驗的,下面我們先看一下源碼,再去實現自定義的認證提供商類AuthenticationProvider。
在源碼中,AuthenticationProvider是一個接口,里面有兩個方法,一個是校驗參數的方法,另一個則是根據當前認證信息匹配出對應的認證提供商類,這個接口有很多實現類,其中ProviderManager類是非常關鍵的,在這個類中的參數校驗方法中,會根據當前要認證的對象,獲取符合要求的所有的認證提供商,然后循環匹配出對應的認證提供商,在調取校驗方法進行參數校驗

(6)由上圖可知,我們只需要實現ProviderManager接口的兩個方法,自定義自己的參數校驗方法,並且把這個自定義的ProviderManager加入到認證提供商集合中,在循環匹配的時候即可匹配到我們自定義的ProviderManager,進行參數校驗。

(7)把自定義的ProviderManager放入ProviderManager集合中,我的方法如下,在配置文件中,重寫configure方法,配置ProviderManager,這里除了配置我們自定義的ProviderManager之外,還需要額外配置默認的密碼授權模式的ProviderManager,否則client認證將不會通過。


(8)經過以上步驟,已經實現自定義token的獲取,最后要做的是把這個自定義授權模式類,放入系統默認的授權模式集合中,這樣在CompositeTokenGranter的grant方法中,才能循環匹配到我們自定義的授權模式,進而直接獲取token。我們先來看一下,系統默認的授權模式集合是在哪里初始化的?答案在AuthorizationServerEndpointsConfigurer這個類中559行,調用了getDefaultTokenGranters()方法,並且創建了 CompositeTokenGranter的實例對象,進行初始化。查看源碼可以發現,系統已經把默認的授權模式全都寫死在程序里了,因此我的解決思路是如下的
把AuthorizationServerEndpointsConfigurer中,初始化默認授權方式的代碼復制一下,在配置文件中額外重新配置自定義的模式,代碼部分截圖(完整參考最后)如下


(9)授權認證服務端點配置



(10)最后:查看源碼可以發現,當前clientid擁有的授權方式碼是通過ClientDetails client = clientDetailsService.loadClientByClientId(clientId);獲取的,在數據庫中配置的,因此我們需要再oauth_client_details表中,在對應的 clientid 的 authorized_grant_types 字段中加上自定義的授權模式碼。
第一部分:關於授權類型 grant_type 的解析
- 每種
grant_type都會有一個對應的TokenGranter實現類。 - 所有
TokenGranter實現類都通過CompositeTokenGranter中的tokenGranters集合存起來。 - 然后通過判斷
grantType參數來定位具體使用那個TokenGranter實現類來處理授權。
第二部分:關於授權登錄邏輯
- 每種
授權方式都會有一個對應的AuthenticationProvider實現類來實現。 - 所有
AuthenticationProvider實現類都通過ProviderManager中的providers集合存起來。 TokenGranter類會 new 一個AuthenticationToken實現類,如UsernamePasswordAuthenticationToken傳給ProviderManager類。- 而
ProviderManager則通過AuthenticationToken來判斷具體使用那個AuthenticationProvider實現類來處理授權。 - 具體的登錄邏輯由
AuthenticationProvider實現類來實現,如DaoAuthenticationProvider
完整代碼:
OpenIdTokenGranter
package com.lpw.CustomerSecurity; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.provider.*; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import java.util.LinkedHashMap; import java.util.Map; /** * @Author dw * @ClassName OpenIdTokenGranter * @Description * @Date 2020/9/15 13:19 * @Version 1.0 */ public class OpenIdTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "openId"; private final AuthenticationManager authenticationManager; public OpenIdTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices , ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); this.authenticationManager = authenticationManager; } @Override protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters()); Authentication userAuth = new OpenIdAuthenticationToken(parameters); ((AbstractAuthenticationToken) userAuth).setDetails(parameters); userAuth = authenticationManager.authenticate(userAuth); if (userAuth == null || !userAuth.isAuthenticated()) { throw new InvalidGrantException("Could not authenticate openId: " + parameters); } OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); return new OAuth2Authentication(storedOAuth2Request, userAuth); } }
package com.lpw.CustomerSecurity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.*; import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter; import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices; import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter; import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter; import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @Author dw * @ClassName TokenGranterConfig * @Description token授權模式配置類 * @Date 2020/9/15 13:26 * @Version 1.0 */ @Configuration public class TokenGranterConfig { @Autowired private ClientDetailsService clientDetailsService; @Autowired private OpenIdUserDetailService userDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private TokenStore tokenStore; private AuthorizationCodeServices authorizationCodeServices; private boolean reuseRefreshToken = true; private AuthorizationServerTokenServices tokenServices; private TokenGranter tokenGranter; /** * 授權模式 * * @return */ @Bean public TokenGranter tokenGranter() { if (tokenGranter == null) { tokenGranter = new TokenGranter() { private CompositeTokenGranter delegate; @Override public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { if (delegate == null) { delegate = new CompositeTokenGranter(getDefaultTokenGranters()); } return delegate.grant(grantType, tokenRequest); } }; } return tokenGranter; } /** * 程序支持的授權類型 * * @return */ private List<TokenGranter> getDefaultTokenGranters() { AuthorizationServerTokenServices tokenServices = tokenServices(); AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices(); OAuth2RequestFactory requestFactory = requestFactory(); List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>(); // 添加授權碼模式 tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory)); // 添加刷新令牌的模式 tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory)); // 添加隱式授權模式 tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory)); // 添加客戶端模式 tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory)); // 添加自定義Openid授權模式 tokenGranters.add(new OpenIdTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); if (authenticationManager != null) { // 添加密碼模式 tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory)); } return tokenGranters; } /** * TokenServices * * @return */ private AuthorizationServerTokenServices tokenServices() { if (tokenServices != null) { return tokenServices; } this.tokenServices = createDefaultTokenServices(); return tokenServices; } /** * 授權碼API * * @return */ private AuthorizationCodeServices authorizationCodeServices() { if (authorizationCodeServices == null) { authorizationCodeServices = new InMemoryAuthorizationCodeServices(); } return authorizationCodeServices; } /** * OAuth2RequestFactory的默認實現,它初始化參數映射中的字段, * 驗證授權類型(grant_type)和范圍(scope),並使用客戶端的默認值填充范圍(scope)(如果缺少這些值)。 * * @return */ private OAuth2RequestFactory requestFactory() { return new DefaultOAuth2RequestFactory(clientDetailsService); } /** * 默認 TokenService * * @return */ private DefaultTokenServices createDefaultTokenServices() { DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(tokenStore); tokenServices.setSupportRefreshToken(true); tokenServices.setReuseRefreshToken(reuseRefreshToken); tokenServices.setClientDetailsService(clientDetailsService); addUserDetailsService(tokenServices, this.userDetailsService); return tokenServices; } /** * 添加預身份驗證 * * @param tokenServices * @param userDetailsService */ 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))); } } }
package com.lpw.CustomerSecurity; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import java.util.Collection; /** * @Author dw * @ClassName OpenIdAuthenticationToken * @Description * @Date 2020/9/15 13:09 * @Version 1.0 */ public class OpenIdAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private final Object principal; public OpenIdAuthenticationToken(Object principal) { super(null); this.principal = principal; setAuthenticated(false); } public OpenIdAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; super.setAuthenticated(true); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return this.principal; } @Override public void setAuthenticated(boolean isAuthenticated) { if (isAuthenticated) { throw new IllegalArgumentException( "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } super.setAuthenticated(false); } @Override public void eraseCredentials() { super.eraseCredentials(); } }
package com.lpw.CustomerSecurity; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import java.util.Map; /** * @Author dw * @ClassName OpenIdAuthenticationProvider * @Description * @Date 2020/9/15 13:14 * @Version 1.0 */ public class OpenIdAuthenticationProvider implements AuthenticationProvider { private CustomUserDetailsService customUserDetailsService; @Override public Authentication authenticate(Authentication authentication) { OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication; Map<String, String> principal = (Map<String, String>) authenticationToken.getPrincipal(); UserDetails userDetails = customUserDetailsService.loadUserByUsername(principal); if (userDetails == null) { throw new InternalAuthenticationServiceException("openId錯誤"); } OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(userDetails, userDetails.getAuthorities()); // 設置前端傳過來的具體參數 authenticationResult.setDetails(authenticationToken.getDetails()); return authenticationResult; } @Override public boolean supports(Class<?> authentication) { return OpenIdAuthenticationToken.class.isAssignableFrom(authentication); } public CustomUserDetailsService getCustomUserDetailsService() { return customUserDetailsService; } public void setCustomUserDetailsService(CustomUserDetailsService customUserDetailsService) { this.customUserDetailsService = customUserDetailsService; } }
package com.lpw.CustomerSecurity; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.lpw.Enum.WeChatUserParamEnum; import com.lpw.dao.*; import com.lpw.entity.*; import com.lpw.utils.DateUtil; import com.lpw.utils.WeChatUtil; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Map; import java.util.Objects; /** * @Author dw * @ClassName OpenIdUserDetailService * @Description 微信小程序授權,注冊,獲取用戶信息 * @Date 2020/9/15 17:03 * @Version 1.0 */ @Service public class OpenIdUserDetailService implements CustomUserDetailsService { @Autowired private IExhibitionDao exhibitionDao; @Autowired private IUserMapperDao userMapperDao; @Autowired private IVisitorExhibitionDao visitorExhibitionDao; @Autowired private IVisitorDao visitorDao; @Autowired private IVisitorUserDao visitorUserDao; @Override public UserDetails loadUserByUsername(String userName, String userType) throws UsernameNotFoundException { return null; } @Override public UserDetails loadUserByUsername(Map<String, String> principal) throws UsernameNotFoundException { // 獲取微信用戶授權的信息 User weChatUserInfo = getWeChatUserInfo(principal); String exhibitionId = principal.get(WeChatUserParamEnum.EXHIBITION_ID.getValue()); // 檢查用戶信息 userIsRegistered(weChatUserInfo, exhibitionId); // 查詢用戶 VisitorUser visitorUser = visitorUserDao.queryVisitorUser(weChatUserInfo.getLoginName()); return visitorUser; } @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { return null; } /** * 獲取微信用戶信息 */ public User getWeChatUserInfo(Map<String, String> principal) { String code = principal.get(WeChatUserParamEnum.CODE.getValue()); String exhibitionId = principal.get(WeChatUserParamEnum.EXHIBITION_ID.getValue()); String rawData = principal.get(WeChatUserParamEnum.RAW_DATA.getValue()); // String encrypteData = principal.get(WeChatUserParamEnum.ENCRYPTE_DATA.getValue()); // String iv = principal.get(WeChatUserParamEnum.IV.getValue()); String signature = principal.get(WeChatUserParamEnum.SIGNATURE.getValue()); // 查詢當前小程序的配置信息 Exhibition exhibition = exhibitionDao.queryExhibitionWechatConf(Integer.valueOf(exhibitionId)); // 2.開發者服務器 登錄憑證校驗接口 appId + appSecret + 接收小程序發送的code WeChatUtil weChatUtil = new WeChatUtil(exhibition); JSONObject SessionKeyOpenId = weChatUtil.getSessionKeyOrOpenId(code); // 3.接收微信接口服務 獲取返回的參數 String openid = SessionKeyOpenId.get("openid", String.class); String sessionKey = SessionKeyOpenId.get("session_key", String.class); // 用戶非敏感信息:rawData // 簽名:signature JSONObject rawDataJson = JSONUtil.parseObj(rawData); // 4.校驗簽名 小程序發送的簽名signature與服務器端生成的簽名signature2 = sha1(rawData + sessionKey) String signature2 = DigestUtils.sha1Hex(rawData + sessionKey); if (!signature.equals(signature2)) { throw new UsernameNotFoundException("簽名不匹配"); } //encrypteData比rowData多了appid和openid // JSONObject userInfo = weChatUtil.getUserInfo(encrypteData, // sessionKey, iv); User user = new User(); user.setLoginName(openid); user.setName(rawDataJson.get("nickName", String.class)); user.setIcon(rawDataJson.get("avatarUrl", String.class)); // 0:女 1: 男 Integer sex = rawDataJson.get("gender", String.class).equals("女") ? 0 : 1; user.setSex(sex); return user; } /** * 查詢當前訪客是否買需要注冊 */ @Transactional(rollbackForClassName = "RuntimeException") public void userIsRegistered(User weChatUserInfo, String exhibitionId){ // 查詢當前用戶是否存在 MyUser myUser = userMapperDao.loadUserByUsername(weChatUserInfo.getLoginName()); Integer userId = null; if (Objects.isNull(myUser)) { // 新增用戶 weChatUserInfo.setCreateTime(DateUtil.currentDateTime()); userMapperDao.insert(weChatUserInfo); userId = weChatUserInfo.getUserId(); }else { userId = myUser.getUserId(); } // 查詢當前訪客是否存在 QueryWrapper<Visitor> visitorQueryWrapper = new QueryWrapper<>(); visitorQueryWrapper.lambda().eq(Visitor::getUserId, userId); Visitor hasVisitor = visitorDao.selectOne(visitorQueryWrapper); Integer visitorId = null; if(Objects.isNull(hasVisitor)){ // 插入訪客到訪客表 Visitor visitor = new Visitor(); visitor.setUserId(userId); visitor.setCreateTime(DateUtil.currentDateTime()); visitorDao.insert(visitor); visitorId = visitor.getVisitorId(); }else { visitorId = hasVisitor.getVisitorId(); } // 查詢當前訪客是否注冊到了當前展會 QueryWrapper<VisitorExhibition> query = new QueryWrapper<>(); query.lambda().eq(VisitorExhibition::getExhibitionId, exhibitionId); query.lambda().eq(VisitorExhibition::getVisitorId, visitorId); VisitorExhibition visitorExhibition = visitorExhibitionDao.selectOne(query); if (Objects.isNull(visitorExhibition)) { // 當前訪客未注冊當前展會,新增訪客、展會關系 VisitorExhibition visitorExhibition1 = new VisitorExhibition(); visitorExhibition1.setExhibitionId(Integer.valueOf(exhibitionId)); visitorExhibition1.setVisitorId(visitorId); visitorExhibition1.setOpenId(weChatUserInfo.getLoginName()); visitorExhibitionDao.insert(visitorExhibition1); } } }
package com.lpw.CustomerSecurity; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import java.util.Map; /** * @Author dw * @ClassName CustomUserDetailsService * @Description 繼承原來的UserDetailsService新增自定義方法 * @Date 2020/8/19 17:23 * @Version 1.0 */ public interface CustomUserDetailsService extends UserDetailsService { /** * 根據用戶名、用戶類型查詢用戶 * @param userName 用戶名 * @param userType 用戶類型 * @return * @throws UsernameNotFoundException */ UserDetails loadUserByUsername(String userName, String userType) throws UsernameNotFoundException; /** * 根據OpenId、 展會id查詢用戶 * @param principal 前端傳過來的微信小程序授權登錄的參數信息 * @return * @throws UsernameNotFoundException */ UserDetails loadUserByUsername(Map<String, String> principal) throws UsernameNotFoundException; }
package com.lpw.auth; import com.lpw.CustomerSecurity.UserDetailsServiceImpl; import com.lpw.security.CustomWebResponseExceptionTranslator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.TokenGranter; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import javax.sql.DataSource; /** * @Author dw * @ClassName AuthorizationServerConfig * @Description * @Date 2020/8/17 10:46 * @Version 1.0 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private DataSource dataSource; /** * 認證管理器,認證用戶信息 */ @Autowired private AuthenticationManager authenticationManager; /** * 用戶認證處理邏輯 */ @Autowired private UserDetailsServiceImpl myUserDetailService; /** * 認證異常全局處理 */ @Autowired private CustomWebResponseExceptionTranslator webResponseExceptionTranslator; @Autowired private TokenGranter tokenGranter; @Bean public TokenStore tokenStore() { //使用內存中的 token store // return new InMemoryTokenStore(); //使用Jdbctoken store return new JdbcTokenStore(dataSource); } @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } /** * @return * @Author dw * @Description 配置token的過期日期等 * @Date 2020/4/23 18:32 * @Param */ @Bean @Primary public DefaultTokenServices defaultTokenServices() { DefaultTokenServices services = new DefaultTokenServices(); // access token有效期 services.setAccessTokenValiditySeconds(60 * 60 * 24); // refresh token有效期 services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7); // 支持使用refresh token刷新access token services.setSupportRefreshToken(true); // 允許重復使用refresh token services.setReuseRefreshToken(true); services.setTokenStore(tokenStore()); return services; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 配置客戶端, 用於client認證 clients.withClientDetails(clientDetails()); // 第一次使用的時候,需要配置客戶端信息,或者手動添加客戶端信息到數據庫oauth_client_details表中 // clients.jdbc(dataSource) // .withClient("myClient") // .secret(new BCryptPasswordEncoder().encode("123456")) // .authorizedGrantTypes("password", "refresh_token")//允許授權范圍 //// .authorities("ROLE_ADMIN","ROLE_USER")//客戶端可以使用的權限 // .scopes("all") // .accessTokenValiditySeconds(7200) // .refreshTokenValiditySeconds(7200); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()) .tokenServices(defaultTokenServices()) .authenticationManager(authenticationManager) .userDetailsService(myUserDetailService) .reuseRefreshTokens(true) //接收GET和POST .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) //四種授權模式+刷新令牌的模式+自定義授權模式 .tokenGranter(tokenGranter) .exceptionTranslator(webResponseExceptionTranslator); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()") //允許表單登錄 .allowFormAuthenticationForClients(); } public static void main(String[] args) { System.out.println(new BCryptPasswordEncoder().encode("123456")); } }
package com.lpw.security; import com.lpw.CustomerSecurity.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 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.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @Author dw * @ClassName WebSecurityConfig * @Description * @Date 2020/8/19 13:53 * @Version 1.0 */ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private OpenIdUserDetailService openIdUserDetailService; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean(name = "CustomerAuthenticationProvider") public AuthenticationProvider customAuthenticationProvider() { CustomerAuthenticationProvider customAuthenticationProvider = new CustomerAuthenticationProvider(); customAuthenticationProvider.setUserDetailsService(userDetailsService); customAuthenticationProvider.setHideUserNotFoundExceptions(false); customAuthenticationProvider.setPasswordEncoder(passwordEncoder()); return customAuthenticationProvider; } @Bean(name = "openIdAuthenticationProvider") public AuthenticationProvider openIdAuthenticationProvider() { OpenIdAuthenticationProvider customAuthenticationProvider = new OpenIdAuthenticationProvider(); customAuthenticationProvider.setCustomUserDetailsService(openIdUserDetailService); return customAuthenticationProvider; } @Bean(name = "daoAuthenticationProvider") public AuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(userDetailsService); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); return daoAuthenticationProvider; } /** * 自定義的用戶認證 * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(customAuthenticationProvider()) .authenticationProvider(daoAuthenticationProvider()) .authenticationProvider(openIdAuthenticationProvider()); } /** * 用來配置攔截保護的請求 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http //定義哪些url需要被保護 哪些不需要保護 .authorizeRequests() //定義這兩個鏈接不需要登錄可訪問 .antMatchers("/oauth/token", "oauth/check_token", "/v2/api-docs", "/swagger-resources/configuration/ui", "/swagger-resources", "/swagger-resources/configuration/security", "/swagger-ui.html", "/course/coursebase/**", "/webjars/**").permitAll() //定義所有的都不需要登錄 目前是測試需要 // .antMatchers("/**").permitAll() .anyRequest().authenticated() //其他的都需要登錄 //.antMatchers("/sys/**").hasRole("admin")///sys/**下的請求 需要有admin的角色 .and() .formLogin() // .loginPage("/login") //如果未登錄則跳轉登錄的頁面 這兒可以控制登錄成功和登錄失敗跳轉的頁面 .and() .csrf().disable();//防止跨站請求 spring security中默認開啟 } }
