1、使用的技術以及版本號碼 JDK8.0 Spring 5.0 oauth2.0 redis2.0
2、項目採用MAVEN管理。 pom檔案中加入: <
dependency > <
groupId > org.springframework.cloud </
groupId > <
artifactId > spring-cloud-starter-security </
artifactId > </
dependency >
<
dependency > <
groupId > org.springframework.cloud </
groupId > <
artifactId > spring-cloud-starter-oauth2 </
artifactId > </
dependency >
<
dependency > <
groupId > org.springframework.boot </
groupId > <
artifactId > spring-boot-starter-data-redis </
artifactId > </
dependency >
<
dependency > <
groupId > org.springframework.boot </
groupId > <
artifactId > spring-boot-starter-data-jpa </
artifactId > </
dependency >
<
dependency > <
groupId > org.thymeleaf </
groupId > <
artifactId > thymeleaf-spring5 </
artifactId > </
dependency >
3、資料的儲存 現採用Redis來進行儲存。所以決定token儲存在redis中,client資訊和code資訊儲存在資料庫表中。 OAUTH2.0涉及到的資料庫表有詳細說明: http://andaily.com/spring-oauth-server/db_table_description.html
4、 Spring5.0 新版本中,有很多的方法和類發生了改變,導致現在網上大多數的認證代碼不能使用,運行報錯。經過查詢與研究。終於運行起來了。需要注意的有: RedisConnectionFactory儲存token的時候會出現錯誤,這個時候需要自訂MyRedisTokenStore類,實現TokenStore。MyRedisTokenStore和RedisTokenStore代碼差不多一樣,只是把所有conn.set(…)都換成conn..stringCommands().set(…), PasswordEncoder密碼驗證clientId的時候會報錯,因為5.0新特性中需要在密碼前方需要加上{Xxx}來判別。所以需要自訂一個類,重新BCryptPasswordEncoder的match方法。
總之坑很多,我們只有一步一個坑慢慢的前行去填坑。
5、
相關學習資料 oauth2.0+security相關資料: https://www.cnblogs.com/sheng-jie/p/6564520.html https://developers.douban.com/wiki/?title=oauth2 http://andaily.com/spring-oauth-server/db_table_description.html http://blog.csdn.net/greatneyo/article/details/77979848 http://www.tianshouzhi.com/api/tutorials/spring_security_4/264 https://www.cnblogs.com/xingxueliao/p/5911292.html https://docs.spring.io/spring-security/site/docs/3.2.0.RC2/apidocs/org/springframework/security/config/annotation/web/builders/HttpSecurity.html
出現bug,需要尋找解決辦法的時候,如果在百度裡面沒找到相關的資料,可以在 https://stackoverflow.com/ 中去尋找。
reids2.0相關資料: https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#new-in-2.0.0 。
6、核心代碼:
package com.hbasesoft.vcc.sgp.ability.oauth.server.config;import com.hbasesoft.framework.db.core.config.DbParam;import com.hbasesoft.framework.db.core.utils.DataSourceUtil;import com.hbasesoft.vcc.sgp.ability.oauth.server.security.MyBCryptPasswordEncoder;import com.hbasesoft.vcc.sgp.ability.oauth.server.security.MyRedisTokenStore;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.crypto.password.PasswordEncoder;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.approval.ApprovalStore;import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;import javax.sql.DataSource;/** * @Author: fb * @Description client資訊通過jdbc去查詢資料庫驗證,(注釋部分是通過Redis方法去驗證) * @Date: Create in 14:54 2018/2/1 * @Modified By */@Configuration@EnableAuthorizationServerpublic class OAuthServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; //本系統獲得dataSource的方式 private DataSource dataSource = DataSourceUtil.registDataSource("master", new DbParam("master")); @Autowired private RedisConnectionFactory connectionFactory; @Bean public PasswordEncoder passwordEncoder() { return new MyBCryptPasswordEncoder(); } /* @Autowired private ClientDetailsService clientDetailsService; */ @Bean public MyRedisTokenStore tokenStore() { return new MyRedisTokenStore(connectionFactory); } @Bean public AuthorizationCodeServices authorizationCodeServices() { return new JdbcAuthorizationCodeServices(dataSource); } @Bean public ClientDetailsService clientDetailsService() { return new JdbcClientDetailsService(dataSource); } @Bean public ApprovalStore approvalStore() { TokenApprovalStore store = new TokenApprovalStore(); store.setTokenStore(tokenStore()); return store; } /* @Bean public UserApprovalHandler userApprovalHandler() throws Exception { MyUserApprovalHandler handler = new MyUserApprovalHandler(); handler.setApprovalStore(approvalStore()); handler.setClientDetailsService(clientDetailsService); handler.setRequestFactory(new DefaultOAuth2RequestFactory( clientDetailsService)); handler.setUseApprovalStore(true); return handler; } */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception {// //用戶端資訊通過Redis去取得驗證// final RedisClientDetailsServiceBuilder builder = new RedisClientDetailsServiceBuilder();// clients.setBuilder(builder); //通過JDBC去查詢資料庫oauth_client_details表驗證clientId資訊 clients.jdbc(this.dataSource).clients(this.clientDetailsService()); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .tokenStore(this.tokenStore()) // .userDetailsService(userDetailsService) // .userApprovalHandler(userApprovalHandler()) .authorizationCodeServices(this.authorizationCodeServices()); } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()"); }}
以上代碼的dataSource是我們架構系統封裝好的,如果你們dataSource沒有封裝好,則直接用
@Autowired private DataSource dataSource;
MyBCryptPasswordEncoder類是我自訂的一個類,用來重新match方法。
package com.hbasesoft.vcc.sgp.ability.oauth.server.security;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;/** * <Description> <br> * * @author fb<br> * @version 1.0<br> * @taskId <br> * @CreateDate Create in 13:50 2018/2/12 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.security <br> * @since V1.0<br> */public class MyBCryptPasswordEncoder extends BCryptPasswordEncoder { @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); String presentedPassword =passwordEncoder.encode(encodedPassword); return passwordEncoder.matches(rawPassword, presentedPassword); }}
上面的類中,client資訊採用的是儲存在資料庫中,而token資訊,則採用redis來儲存,redis用RedisTokenStore來實現,但是由於在Spring的5.0中會出現如下錯誤:
我將我的spring boot項目版本升到2.0.0.M7後,整合了spring security oauth2(預設版本),redis(預設版本),並且用redis來儲存token。項目正常啟動後,請求token時報錯
nested exception is java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V
看到報錯資訊,我的第一反應是版本衝突,但是我的依賴的都是spring-boot-starter-parent中的預設版本。 pring-data-redis 2.0版本中set(String,String)被棄用了。然後我按照網頁中的決解方法“spring-date-redis”改為2.0.1.RELEASE和”jedis“為2.9.0(顯式聲明),結果還是報一樣的錯。用了一個極端的方式解決的。spring-data-redis 2.0版本中set(String,String)被棄用了,要使用RedisConnection.stringCommands().set(…),所有我自訂一個RedisTokenStore,代碼和RedisTokenStore一樣,只是把所有conn.set(…)都換成conn..stringCommands().set(…),測試後方法可行。
MyRedisTokenStore.java類代碼如下:
package com.hbasesoft.vcc.sgp.ability.oauth.server.security;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;import java.util.Date;import java.util.Iterator;import java.util.List;import org.springframework.data.redis.connection.RedisConnection;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;import org.springframework.security.oauth2.common.OAuth2AccessToken;import org.springframework.security.oauth2.common.OAuth2RefreshToken;import org.springframework.security.oauth2.provider.OAuth2Authentication;import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;import org.springframework.security.oauth2.provider.token.TokenStore;import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy;import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy;import org.springframework.stereotype.Component;import java.util.*;/** * <Description> 重寫tokenStore .因為最新版中RedisTokenStore的set已經被棄用了, * 所以我就只能自訂一個,代碼和RedisTokenStore一樣, * 只是把所有conn.set(…)都換成conn..stringCommands().set(…), * <br> * * @author fb<br> * @version 1.0<br> * @taskId <br> * @CreateDate Create in 10:59 2018/2/13 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.security <br> * @since V1.0<br> */@Componentpublic class MyRedisTokenStore implements TokenStore { private static final String ACCESS = "access:"; private static final String AUTH_TO_ACCESS = "auth_to_access:"; private static final String AUTH = "auth:"; private static final String REFRESH_AUTH = "refresh_auth:"; private static final String ACCESS_TO_REFRESH = "access_to_refresh:"; private static final String REFRESH = "refresh:"; private static final String REFRESH_TO_ACCESS = "refresh_to_access:"; private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:"; private static final String UNAME_TO_ACCESS = "uname_to_access:"; private final RedisConnectionFactory connectionFactory; private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator(); private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy(); private String prefix = ""; public MyRedisTokenStore(RedisConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; } public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) { this.authenticationKeyGenerator = authenticationKeyGenerator; } public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) { this.serializationStrategy = serializationStrategy; } public void setPrefix(String prefix) { this.prefix = prefix; } private RedisConnection getConnection() { return this.connectionFactory.getConnection(); } private byte[] serialize(Object object) { return this.serializationStrategy.serialize(object); } private byte[] serializeKey(String object) { return this.serialize(this.prefix + object); } private OAuth2AccessToken deserializeAccessToken(byte[] bytes) { return (OAuth2AccessToken)this.serializationStrategy.deserialize(bytes, OAuth2AccessToken.class); } private OAuth2Authentication deserializeAuthentication(byte[] bytes) { return (OAuth2Authentication)this.serializationStrategy.deserialize(bytes, OAuth2Authentication.class); } private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) { return (OAuth2RefreshToken)this.serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class); } private byte[] serialize(String string) { return this.serializationStrategy.serialize(string); } private String deserializeString(byte[] bytes) { return this.serializationStrategy.deseri