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相关资料:

出现bug,需要查找解决办法的时候,如果在百度里面没找到相关的资料,可以在 https://stackoverflow.com/ 中去寻找。


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;

/**
 * <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>
 */
@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<Object> 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<Object> 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<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
        byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(clientId, userName));
        List<byte[]> byteList = null;
        RedisConnection conn = getConnection();
        try {
            byteList = conn.lRange(approvalKey, 0, -1);
        } finally {
            conn.close();
        }
        if (byteList == null || byteList.size() == 0) {
            return Collections.<OAuth2AccessToken> emptySet();
        }
        List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(byteList.size());
        for (byte[] bytes : byteList) {
            OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
            accessTokens.add(accessToken);
        }
        return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens);
    }

    @Override
    public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
        byte[] key = serializeKey(CLIENT_ID_TO_ACCESS + clientId);
        List<byte[]> byteList = null;
        RedisConnection conn = getConnection();
        try {
            byteList = conn.lRange(key, 0, -1);
        } finally {
            conn.close();
        }
        if (byteList == null || byteList.size() == 0) {
            return Collections.<OAuth2AccessToken> emptySet();
        }
        List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(byteList.size());
        for (byte[] bytes : byteList) {
            OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
            accessTokens.add(accessToken);
        }
        return Collections.<OAuth2AccessToken> 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<GrantedAuthority> 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<GrantedAuthority> getAuthorities() {
		return this.authorities;
	}

	public void setAuthorities(
			Collection<? extends GrantedAuthority> 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<GrantedAuthority> 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;

/**
 * <Description> <br>
 *
 * @author fb<br>
 * @version 1.0<br>
 * @taskId <br>
 * @CreateDate Create in 11:18 2018/2/11
 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.service.impl <br>
 * @since V1.0<br>
 */
public class UserDetailsImpl extends User{
    public UserDetailsImpl(String username, String password, Collection<? extends GrantedAuthority> 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;

/**
 * <Description> 登录验证授权 <br>
 *
 * @author fb<br>
 * @version 1.0<br>
 * @taskId <br>
 * @CreateDate Create in 11:38 2018/2/9
 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.controller <br>
 * @since V1.0<br>
 */
@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;

/**
 * <Description> <br>
 *
 * @author fb<br>
 * @version 1.0<br>
 * @taskId <br>
 * @CreateDate Create in 10:51 2018/2/11
 * @see com.hbasesoft.vcc.sgp.ability.oauth.server.api <br>
 * @since V1.0<br>
 */
@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);
}

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中。

努力奋斗!














版权声明:本文为fanbojiayou原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。