Spring 5.0+Spring Boot+security+spring cloud oauth2+Redis整合详情,记录那些遇到的一些坑

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
@EnableAuthorizationServer
public 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;

/**
 *  
* * @author fb
* @version 1.0
* @taskId
* @CreateDate Create in 13:50 2018/2/12 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.security
* @since V1.0
*/ 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.*;

/**
 *  重写tokenStore .因为最新版中RedisTokenStore的set已经被弃用了,
 * 所以我就只能自定义一个,代码和RedisTokenStore一样,
 * 只是把所有conn.set(…)都换成conn..stringCommands().set(…),
 * 
* * @author fb
* @version 1.0
* @taskId
* @CreateDate Create in 10:59 2018/2/13 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.security
* @since V1.0
*/ @Component public 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.deserializeString(bytes); } @Override public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { String key = this.authenticationKeyGenerator.extractKey(authentication); byte[] serializedKey = this.serializeKey(AUTH_TO_ACCESS + key); byte[] bytes = null; RedisConnection conn = this.getConnection(); try { bytes = conn.get(serializedKey); } finally { conn.close(); } OAuth2AccessToken accessToken = this.deserializeAccessToken(bytes); if (accessToken != null) { OAuth2Authentication storedAuthentication = this.readAuthentication(accessToken.getValue()); if (storedAuthentication == null || !key.equals(this.authenticationKeyGenerator.extractKey(storedAuthentication))) { this.storeAccessToken(accessToken, authentication); } } return accessToken; } @Override public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { return this.readAuthentication(token.getValue()); } @Override public OAuth2Authentication readAuthentication(String token) { byte[] bytes = null; RedisConnection conn = this.getConnection(); try { bytes = conn.get(this.serializeKey("auth:" + token)); } finally { conn.close(); } OAuth2Authentication auth = this.deserializeAuthentication(bytes); return auth; } @Override public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { return this.readAuthenticationForRefreshToken(token.getValue()); } public OAuth2Authentication readAuthenticationForRefreshToken(String token) { RedisConnection conn = getConnection(); try { byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token)); OAuth2Authentication auth = deserializeAuthentication(bytes); return auth; } finally { conn.close(); } } @Override public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { byte[] serializedAccessToken = serialize(token); byte[] serializedAuth = serialize(authentication); byte[] accessKey = serializeKey(ACCESS + token.getValue()); byte[] authKey = serializeKey(AUTH + token.getValue()); byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication)); byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); RedisConnection conn = getConnection(); try { conn.openPipeline(); conn.stringCommands().set(accessKey, serializedAccessToken); conn.stringCommands().set(authKey, serializedAuth); conn.stringCommands().set(authToAccessKey, serializedAccessToken); if (!authentication.isClientOnly()) { conn.rPush(approvalKey, serializedAccessToken); } conn.rPush(clientId, serializedAccessToken); if (token.getExpiration() != null) { int seconds = token.getExpiresIn(); conn.expire(accessKey, seconds); conn.expire(authKey, seconds); conn.expire(authToAccessKey, seconds); conn.expire(clientId, seconds); conn.expire(approvalKey, seconds); } OAuth2RefreshToken refreshToken = token.getRefreshToken(); if (refreshToken != null && refreshToken.getValue() != null) { byte[] refresh = serialize(token.getRefreshToken().getValue()); byte[] auth = serialize(token.getValue()); byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue()); conn.stringCommands().set(refreshToAccessKey, auth); byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue()); conn.stringCommands().set(accessToRefreshKey, refresh); if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; Date expiration = expiringRefreshToken.getExpiration(); if (expiration != null) { int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L) .intValue(); conn.expire(refreshToAccessKey, seconds); conn.expire(accessToRefreshKey, seconds); } } } conn.closePipeline(); } finally { conn.close(); } } private static String getApprovalKey(OAuth2Authentication authentication) { String userName = authentication.getUserAuthentication() == null ? "": authentication.getUserAuthentication().getName(); return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName); } private static String getApprovalKey(String clientId, String userName) { return clientId + (userName == null ? "" : ":" + userName); } @Override public void removeAccessToken(OAuth2AccessToken accessToken) { this.removeAccessToken(accessToken.getValue()); } @Override public OAuth2AccessToken readAccessToken(String tokenValue) { byte[] key = serializeKey(ACCESS + tokenValue); byte[] bytes = null; RedisConnection conn = getConnection(); try { bytes = conn.get(key); } finally { conn.close(); } OAuth2AccessToken accessToken = deserializeAccessToken(bytes); return accessToken; } public void removeAccessToken(String tokenValue) { byte[] accessKey = serializeKey(ACCESS + tokenValue); byte[] authKey = serializeKey(AUTH + tokenValue); byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); RedisConnection conn = getConnection(); try { conn.openPipeline(); conn.get(accessKey); conn.get(authKey); conn.del(accessKey); conn.del(accessToRefreshKey); // Don't remove the refresh token - it's up to the caller to do that conn.del(authKey); List results = conn.closePipeline(); byte[] access = (byte[]) results.get(0); byte[] auth = (byte[]) results.get(1); OAuth2Authentication authentication = deserializeAuthentication(auth); if (authentication != null) { String key = authenticationKeyGenerator.extractKey(authentication); byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key); byte[] unameKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); conn.openPipeline(); conn.del(authToAccessKey); conn.lRem(unameKey, 1, access); conn.lRem(clientId, 1, access); conn.del(serialize(ACCESS + key)); conn.closePipeline(); } } finally { conn.close(); } } @Override public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue()); byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue()); byte[] serializedRefreshToken = serialize(refreshToken); RedisConnection conn = getConnection(); try { conn.openPipeline(); conn.stringCommands().set(refreshKey, serializedRefreshToken); conn.stringCommands().set(refreshAuthKey, serialize(authentication)); if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; Date expiration = expiringRefreshToken.getExpiration(); if (expiration != null) { int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L) .intValue(); conn.expire(refreshKey, seconds); conn.expire(refreshAuthKey, seconds); } } conn.closePipeline(); } finally { conn.close(); } } @Override public OAuth2RefreshToken readRefreshToken(String tokenValue) { byte[] key = serializeKey(REFRESH + tokenValue); byte[] bytes = null; RedisConnection conn = getConnection(); try { bytes = conn.get(key); } finally { conn.close(); } OAuth2RefreshToken refreshToken = deserializeRefreshToken(bytes); return refreshToken; } @Override public void removeRefreshToken(OAuth2RefreshToken refreshToken) { this.removeRefreshToken(refreshToken.getValue()); } public void removeRefreshToken(String tokenValue) { byte[] refreshKey = serializeKey(REFRESH + tokenValue); byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue); byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue); byte[] access2RefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue); RedisConnection conn = getConnection(); try { conn.openPipeline(); conn.del(refreshKey); conn.del(refreshAuthKey); conn.del(refresh2AccessKey); conn.del(access2RefreshKey); conn.closePipeline(); } finally { conn.close(); } } @Override public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { this.removeAccessTokenUsingRefreshToken(refreshToken.getValue()); } private void removeAccessTokenUsingRefreshToken(String refreshToken) { byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken); List results = null; RedisConnection conn = getConnection(); try { conn.openPipeline(); conn.get(key); conn.del(key); results = conn.closePipeline(); } finally { conn.close(); } if (results == null) { return; } byte[] bytes = (byte[]) results.get(0); String accessToken = deserializeString(bytes); if (accessToken != null) { removeAccessToken(accessToken); } } public Collection findTokensByClientIdAndUserName(String clientId, String userName) { byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(clientId, userName)); List byteList = null; RedisConnection conn = getConnection(); try { byteList = conn.lRange(approvalKey, 0, -1); } finally { conn.close(); } if (byteList == null || byteList.size() == 0) { return Collections. emptySet(); } List accessTokens = new ArrayList(byteList.size()); for (byte[] bytes : byteList) { OAuth2AccessToken accessToken = deserializeAccessToken(bytes); accessTokens.add(accessToken); } return Collections. unmodifiableCollection(accessTokens); } @Override public Collection findTokensByClientId(String clientId) { byte[] key = serializeKey(CLIENT_ID_TO_ACCESS + clientId); List byteList = null; RedisConnection conn = getConnection(); try { byteList = conn.lRange(key, 0, -1); } finally { conn.close(); } if (byteList == null || byteList.size() == 0) { return Collections. emptySet(); } List accessTokens = new ArrayList(byteList.size()); for (byte[] bytes : byteList) { OAuth2AccessToken accessToken = deserializeAccessToken(bytes); accessTokens.add(accessToken); } return Collections. unmodifiableCollection(accessTokens); } }

认证类:

package com.hbasesoft.vcc.sgp.ability.oauth.server.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;



/**
 * @Author: fb
 * @Description 访问权限配置
 *  order = 3的过滤器链
 * @Date: Create in 10:36 2018/2/1
 * @Modified By
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.csrf().disable();
		http.requestMatchers().antMatchers("/api/**").and().authorizeRequests()
				.anyRequest().authenticated();
	}
}
在Spring5.0中,WebMvcConfigurerAdapter已经弃用,替代类:WebMvcConfigurationSupport或者DelegatingWebMvcConfiguration)
* extends WebMvcConfigurerAdapter+@EnableWebMvc 等同于 extends WebMvcConfigurationSupport
* 切勿使用@EnableWebMvc和 extends WebMvcConfigurationSupport 在一起。
package com.hbasesoft.vcc.sgp.ability.oauth.server.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * @Author: fb
 * @Description 配置页面视图
 * (在Spring5.0中,WebMvcConfigurerAdapter已经弃用,替代类:WebMvcConfigurationSupport或者DelegatingWebMvcConfiguration)
 *  extends WebMvcConfigurerAdapter+@EnableWebMvc 等同于 extends WebMvcConfigurationSupport
 *  切勿使用@EnableWebMvc和 extends WebMvcConfigurationSupport 在一起
 * @Date: Create in 9:36 2018/2/2
 * @Modified By
 */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.hbasesoft.vcc.sgp.ability.oauth.server"})
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("authorize");
        //配置授权确认页面视图
        registry.addViewController("/oauth/confirm_access").setViewName("authorize");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/index").addResourceLocations("/index.html");
    }
}
package com.hbasesoft.vcc.sgp.ability.oauth.server.config;

import com.hbasesoft.vcc.sgp.ability.oauth.server.security.AdminAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;

/**
 * @Author: fb
 * @Description 访问权限配置(URL的认证。可配置拦截什么URL,设置什么权限等安全限制)
 *  order = 0的过滤器链
 * @Date: Create in 10:36 2018/2/1
 * @Modified By
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Bean
    public AdminAuthenticationProvider adminAuthenticationProvider() {
        AdminAuthenticationProvider provider = new AdminAuthenticationProvider();
        return provider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //在内存中创建了一个用户,该用户的名称为user,密码为password,用户角色为USER。
        //auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
        auth.authenticationProvider(this.adminAuthenticationProvider());
    }

    @Bean(name = "authenticationManager")
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //默认配置(所有认证请求都需要授权,要求用户在进入你的应用的任何URL之前都进行验证)
        /*
        http.authorizeRequests().anyRequest().authenticated().and()
                .formLogin().and()
                .httpBasic();
         */
        //配置进入以下URL之前不需要验证
        http.csrf().disable();
        http.authorizeRequests().antMatchers( "/admin/login.html", "/admin/login",
                 "/agreement.html", "/error.html").permitAll();
        //通过 formLogin() 定义当需要用户登录时候,转到的登录页面
       // http.formLogin().loginPage("/login").permitAll().and().authorizeRequests().anyRequest().authenticated();
    }

}

上面代码中,用户和密码的信息验证通过动态查询数据库来验证。不采用内存中写死。

所以自定义了AdminAuthenticationProvider 和AdminAuthenticationToken类。

package com.hbasesoft.vcc.sgp.ability.oauth.server.security;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.ArrayList;
import java.util.Collection;

public class AdminAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = -3625665688626567368L;

	private Collection authorities = new ArrayList<>();
	private Object principal;
	private Object credentials;
	private String username;

	public AdminAuthenticationToken(String username, String password) {
		super(null);

		this.username = username;
		this.credentials = password;
	}

	public void setPrincipal(Object principal) {
		this.principal = principal;
	}

	@Override
	public Object getPrincipal() {
		return this.principal;
	}

	@Override
	public Object getCredentials() {
		return this.credentials;
	}

	public String getUsername() {
		return this.username;
	}

	@Override
	public Collection getAuthorities() {
		return this.authorities;
	}

	public void setAuthorities(
			Collection authorities) {
		this.authorities.addAll(authorities);
	}

}
package com.hbasesoft.vcc.sgp.ability.oauth.server.security;

import com.hbasesoft.framework.common.utils.logger.LoggerUtil;
import com.hbasesoft.vcc.sgp.ability.oauth.server.service.impl.UserDetailsImpl;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.persistence.Transient;
import java.util.List;


public class AdminAuthenticationProvider implements AuthenticationProvider {
    ;
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @Override
    public boolean supports(Class authentication) {
        return authentication.equals(AdminAuthenticationToken.class);
    }

    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        AdminAuthenticationToken token = (AdminAuthenticationToken) authentication;
        String username = token.getUsername();
        String password = token.getCredentials().toString();
        UserDetails loadedUser = null;
        try {
            loadedUser = new UserDetailsImpl(username,password,this.getGrantedAuthorityList());
        } catch (UsernameNotFoundException notFound) {
            throw notFound;
        } catch (Exception repositoryProblem) {
            throw new InternalAuthenticationServiceException(
                    repositoryProblem.getMessage(), repositoryProblem);
        }
        if (loadedUser == null) {
            LoggerUtil.error("UserDetailsService returned null, which is an interface contract violation.");
            throw new InternalAuthenticationServiceException(
                    "UserDetailsService returned null, which is an interface contract violation");
        }
        try {
            additionalAuthenticationChecks(loadedUser, token);
        } catch (AuthenticationException exception) {
            LoggerUtil.error("Username and password are invalid.", exception);
            throw exception;
        }
        token.setPrincipal(loadedUser);
        token.setDetails(loadedUser);
        token.setAuthorities(loadedUser.getAuthorities());
        authentication.setAuthenticated(true);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return authentication;
    }

    protected void additionalAuthenticationChecks(UserDetails userDetails, AdminAuthenticationToken authentication)
            throws AuthenticationException {

        if (authentication.getCredentials() == null) {
            LoggerUtil.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        String authPassword = authentication.getCredentials().toString();
        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        String presentedPassword =passwordEncoder.encode(authPassword);
        if (!passwordEncoder.matches(userDetails.getPassword(),presentedPassword)) {
            LoggerUtil.debug("Authentication failed: password does not match stored value");
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }


    @Transient
    public List getGrantedAuthorityList() {
        return AuthorityUtils.createAuthorityList("ROLE_USER");
    }



}
package com.hbasesoft.vcc.sgp.ability.oauth.server.service.impl;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;

import javax.persistence.Transient;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;

/**
 *  
* * @author fb
* @version 1.0
* @taskId
* @CreateDate Create in 11:18 2018/2/11 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.service.impl
* @since V1.0
*/ public class UserDetailsImpl extends User{ public UserDetailsImpl(String username, String password, Collection authorities) { super(username, password, authorities); } }

在OAuth2.0授权认证中,生成code信息都需要登录授权才能访问。所以登录验证的代码如下:

package com.hbasesoft.vcc.sgp.ability.oauth.server.controller;

import com.hbasesoft.framework.common.utils.logger.LoggerUtil;
import com.hbasesoft.vcc.sgp.ability.oauth.server.api.LoginAccountClient;
import com.hbasesoft.vcc.sgp.ability.oauth.server.security.AdminAuthenticationToken;
import com.hbasesoft.vcc.sgp.common.controller.CommonConstant;
import com.hbasesoft.vcc.sgp.common.controller.ErrorCodeDef;
import com.hbasesoft.vcc.sgp.uum.user.pojo.DataJsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *  登录验证授权 
* * @author fb
* @version 1.0
* @taskId
* @CreateDate Create in 11:38 2018/2/9 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.controller
* @since V1.0
*/ @RestController @RequestMapping("/oauth") public class AccountController extends BaseController{ @Autowired private AuthenticationManager authenticationManager; @Resource private LoginAccountClient loginAccountClient; @GetMapping(value = "/loginIn") public String loginByAccount(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { String username = getParameter("username", ErrorCodeDef.LOGIN_USERNAME_IS_NULL); String password = getParameter("password", ErrorCodeDef.LOGIN_PASSWORD_IS_NULL); long startTime = System.currentTimeMillis(); LoggerUtil.info("begin login ...."); DataJsonResult result = loginAccountClient.loginVerifyByUserName(username,password); if(result.getCode().equals(CommonConstant.ERROR)){ LoggerUtil.error(result.getMsg()); return "failed"; } try { AdminAuthenticationToken authRequest = new AdminAuthenticationToken(username,password); Authentication authResult = this.authenticationManager.authenticate(authRequest); long endTime = System.currentTimeMillis(); LoggerUtil.info("login successfully."); LoggerUtil.info("AccountController.loginByAccount:"+ (endTime - startTime) / 1000 + "秒"); SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); successHandler.onAuthenticationSuccess(request, response,authResult); return "success"; } catch (AuthenticationException ex) { LoggerUtil.error("login failed.", ex); return "error"; } } }

package com.hbasesoft.vcc.sgp.uum.user.pojo;

/**
 * @Author: fb
 * @Description 数据返回JSON格式
 * @Date: Create in 13:43 2018/2/5
 * @Modified By
 */
public class DataJsonResult {

    private String code;//错误代码

    private String msg;//错误信息描述

    private String ret;//请求是否成功

    private Object data;//返回的数据

    public DataJsonResult(){

    }

    public DataJsonResult(String code, String msg, String ret, Object data) {
        this.code = code;
        this.msg = msg;
        this.ret = ret;
        this.data = data;
    }

    @Override
    public String toString() {
        return "DataJsonResult{" +
                "code='" + code + '\'' +
                ", msg='" + msg + '\'' +
                ", ret='" + ret + '\'' +
                ", data=" + data +
                '}';
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public String getRet() {
        return ret;
    }

    public void setRet(String ret) {
        this.ret = ret;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

LoginAccountClient这个类是调用其他系统的用户信息登录验证。如果你们不需要跨系统调接口,直接在方法中写验证方法就好,LoginAccountClient用到了FeignClient技术。

package com.hbasesoft.vcc.sgp.ability.oauth.server.api;

import com.hbasesoft.vcc.sgp.uum.user.api.LoginAccountRemoteService;
import com.hbasesoft.vcc.sgp.uum.user.pojo.DataJsonResult;
import com.hbasesoft.vcc.sgp.uum.user.pojo.UpmUserLoginRecord;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 *  
* * @author fb
* @version 1.0
* @taskId
* @CreateDate Create in 10:51 2018/2/11 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.api
* @since V1.0
*/ @FeignClient(name="uum-user",url = "${uumServer.uum-url}") public interface LoginAccountClient extends LoginAccountRemoteService{ @RequestMapping(value = "/loginController/loginIn",method = RequestMethod.GET) @Override DataJsonResult loginVerifyByUserName(@RequestParam("username") String username, @RequestParam("password")String password); @RequestMapping(value = "/loginController/saveLoginRecord",method = RequestMethod.POST) @Override String saveloginRecord(@RequestParam("record") UpmUserLoginRecord record); }

Spring 5.0+Spring Boot+security+spring cloud oauth2+Redis整合详情,记录那些遇到的一些坑_第1张图片

application.yml中的配置信息:

project: #项目信息
 name: ABILITY-OAUTH2.0-SERVER
 model: dev
 server:
   plat-component: BOOTSTRAP-PLAT-SERVER
   plat-configuration: BOOTSTRAP-PLAT-SERVER
   uum-user: BOOTSTRAP-UUM-SERVER


server: #系统配置
  port: 18083
  
spring: #应用配置
 application:
  name: ${project.name}

uumServer:
   uum-url: http://localhost:8083/
   
master: #主数据库配置
 db:
  type: mysql
  url: jdbc:mysql://www.hbasesoft.com:3306/sgp_ability?useUnicode=true&characterEncoding=UTF-8&generateSimpleParameterMetadata=true
  username: sgp
  password: sgp
  
cache: #缓存配置
  model: SIMPLE
redis:
  database: 0
  host: 127.0.0.1
  port: 6379
  password:
  timeout: 0
  pool:
      max-active: 8
      max-wait: -1
      max-idle: 8
      min-idle: 0
session:
    store-type: none

eureka: #服务注册中心
 instance:
  hostname: 127.0.0.1
  status-page-url: http://${eureka.instance.hostname}:${server.port}/swagger-ui.html
 client:
  serviceUrl:
   defaultZone: http://127.0.0.1:8080/eureka/


   
   

主要的代码就是这样了。

有疑问,请联系,Spring5.0预计还有很多坑,网上资源较少,特记录下来。

源码到时候上到git中。

努力奋斗!



 
  









你可能感兴趣的:(Spring 5.0+Spring Boot+security+spring cloud oauth2+Redis整合详情,记录那些遇到的一些坑)