栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Cloud整合SpringSecurity OAuth2(全网最强)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Cloud整合SpringSecurity OAuth2(全网最强)

前言

本文是梳理整合SpringCloud和SpringSecurity OAuth2的搭建流程!好久没撸SpringSecurity OAuth2这系列代码了,都快忘了,特写此文章梳理脉络!开干!!!

Maven版本

微服务版本

			2.3.2.RELEASE
			Hoxton.SR9
			2.2.5.RELEASE

			
            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                ${spring-cloud-alibaba.version}
                pom
                import
            

            
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            

SpringSecurity OAuth2版本

		
        
            org.springframework.security.oauth.boot
            spring-security-oauth2-autoconfigure
        

通过微服务版本限定后spring-security-oauth2-autoconfigure的最终版本自动适配为2.1.2

授权码模式

刚开始我这里就不一次性把一大堆配置放上来,需要什么就写什么,不然到时候都搞不清那个配置是干嘛,有什么用的!这也是我写这个文章的缘由!

授权服核心配置-AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

}

SpringSecurity核心配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

}

启动服务器-访问测试
访问http://localhost:3000/oauth/authorize?response_type=code&client_id=tao&redirect_uri=http://baidu.com&scope=all

任意输入账号密码试试

这是因为我们啥也没配置!

配置密码加密
WebSecurityConfig中

  	@Bean
    public PasswordEncoder passwordEncoder() {//密码加密
        return new BCryptPasswordEncoder();
    }

配置登录用户账号密码
WebSecurityConfig中

 	@Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123456")).roles("USER","ADMIN").authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("p1,p2"));
        //这里配置全局用户信息
    }

授权服配置端点信息
AuthorizationServerConfig

@Autowired
    PasswordEncoder passwordEncoder;

@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        //基于内存便于测试
        clients.inMemory()// 使用in-memory存储
                .withClient("tao")// client_id
                //.secret("secret")//未加密
                .secret(passwordEncoder.encode("secret"))//加密
                //.resourceIds("res1")//资源列表
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
                .scopes("all", "ROLE_ADMIN", "ROLE_USER")// 允许的授权范围
                //.autoApprove(false)//false跳转到授权页面
                //加上验证回调地址
                .redirectUris("http://baidu.com");
    }

重启服务测试
http://localhost:3000/oauth/authorize?response_type=code&client_id=tao&redirect_uri=http://baidu.com&scope=all

登录成功得到授权码

授权码获取token

这里授权码就基本搞定了!接下来我们试试密码模式

Unsupported grant type: password,默认不支持密码模式,需要而外配置下!

密码模式

配置认证管理器-AuthenticationManager
WebSecurityConfig中

	@Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

授权服配置密码模式
AuthorizationServerConfig

	@Autowired
    private AuthenticationManager authenticationManager;


	@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {//配置令牌的访问端点和令牌服务
        endpoints
                .authenticationManager(authenticationManager)//认证管理器
               ;

    }

重启访问测试

成功!

简化模式

这个就简单了,token直接是显示在地址栏上的http://localhost:3000/oauth/authorize?client_id=tao&response_type=token&scope=all&redirect_uri=http://baidu.com


客户端模式这里就不演示了,实际上用的并不多!那么到这里,授权基本上的就搞定了,至于其他配置下文会深入,这里我们既然得到了Token那么我们就可以测试一下认证!

认证测试

创建测试资源

@Slf4j
@RestController
@RequestMapping("/mbb")
public class MbbController {

    @GetMapping("/init")
    public R init(){
        return R.ok();
    }

}

@Slf4j
@RestController
@RequestMapping("/oth")
public class OthController {

    @GetMapping("/init")
    public R init(){
        return R.ok();
    }
}

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/init")
    public R init(){
        return R.ok();
    }
    
}

实际上也就是三个请求,/oth/init、/test/init、/test/init

认证测试
我们通过密码模式做授权得到Token

访问测试/oth/init

同样访问其他的也是一样!思考下是什么问题?刚开始检查了下代码以为是WebSecurityConfig中没有配置安全策略,那么我们配置一下!

配置安全策略
WebSecurityConfig中

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers()//系统中所有请求
                .antMatchers("
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")//开放获取tokenkey(这个是做JWT的时候开放的!也就是资源服可以通过这个请求得到JWT加密的key,目前这里没什么用,可以不用配置)
                .checkTokenAccess("permitAll()")//开放远程token检查   /oauth/check_token  body  token
                .allowFormAuthenticationForClients();//允许client使用form的方式进行authentication的授权

    }

其他配置不动!重启服务器测试!
通过密码模式得到Token




这里请求只被资源服接管,所以这里的请求都是符合预期的,这里如果我们有成千上百台资源服,那么每个请求都要远程调用授权服进行认证,那么我们的授权服压力会很大,所以我们可以使用上面代码中提到的JWT,下面我们就开启配置JWT颁发Token!

在搞JWT之前我建议先了解下TokenStore,在了解TokenStore的时候呢顺便有可以了解下AuthorizationCodeServices

AuthorizationCodeServices

SpringSecurity OAuth2关于AuthorizationCodeServices

TokenStore

SpringSecurity OAuth2关于TokenStore

JWT替换默认Token

SpringSecurity OAuth使用JWT替换默认Token

自定义JWT数据

SpringSecurityOAuth2采用JWT生成Token的模式自定义JWT数据

获取JWT中的数据

SpringSecurityOAuth2获取JWT中的数据

那么到这里,我们对token的生成,存储策略有了进一步的了解,那么现在我们的授权服是使用JWT生成Token,同时自定义了一些数据,然后Redis中也存储一份,虽然JWT是自包含,且可以设置过期时间,但是这个是为了满足实际业务,例如,当我们给用户禁用后,然后之前颁发的JWT格式的Token还是能解析出数据,那么这里就不符合业务逻辑,所以这里存储一份在Redis中是为了当我们将用户禁用后,把Redis中的Token也删除,这样用户禁用后携带的老JWT格式Token再到Redis中对比就对比失败,那么这样就能及时更新用户状态信息!接下来我们采用现在的代码,测试一下资源服鉴权

资源服鉴权JWT格式Token

因为我们采用 是JWT格式的Token,那么这个Token中是携带权限信息的,只要配置好资源服,JWT的Token格式资源服已经帮我们实现好了解析!我们按在上面的文章,将JWT格式Token在授权服中配置好后,授权服就不用动了,既然Token中携带了权限信息那么也就意味着资源服无需远程访问授权服进行Token检查,我们改造下资源服核心配置!如下!
改造资源服核心配置


@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    public static final String RESOURCE_ID = "res1";

    @Autowired
    TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_ID)//资源 id
                .tokenStore(tokenStore)
                .stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()//授权的请求
                .anyRequest()//任何请求
                .authenticated()//需要身份认证
                .and().csrf().disable();
    }

}

注意这里有一个tokenStore注入,那么此时需要和授权服保持一致,授权服采用的是Redis那么这里也需要采用Redis,那么这时资源服就知道从哪读取Token然后通过Redis中得到的Token解析对比,所以这个tokenStore至关重要!,通常我们会将tokenStore抽离出来,让资源服和授权服共用同一个tokenStore,那么这样就可以保证tokenStore的存取方式是一致的!

TokenStore

@Slf4j
@Configuration
public class TokenStoreConfig {

    
    String OAUTH_ACCESS = "yy:access:";


    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    
    @Bean
    @ConditionalOnProperty(prefix = "security.oauth2", name = "tokenStore", havingValue = "memory" ,matchIfMissing=true)
    public TokenStore tokenStoreInMemory() {
        log.info("===>"+"tokenStoreInMemory");
        return new InMemoryTokenStore();
    }

    
    @Bean
    @ConditionalOnProperty(prefix = "security.oauth2", name = "tokenStore", havingValue = "redis")
    public TokenStore tokenStoreInRedis() {
        log.info("===>"+"tokenStoreInRedis");
        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        tokenStore.setPrefix(OAUTH_ACCESS);//设置Redis中OAuth相关前缀
        return tokenStore;
    }

}

我这里的是将授权服和资源服分了模块,让将整个SpringSecurity OAuth2共用的配置写到common中,然后授权服和资源服去引用common中的配置,这样后期使用起来非常方便,只需要创建普通额Spring Web项目,需要做授权服的就直接引用授权服依赖即可,需要做资源服额就直接引用资源服额依赖即可,这样保证我们无需开发冗余代码,而且这样使得架构复用性提高!

重启服务器测试资源服检验JWT格式Token
资源权限还是不变

/mbb/init还是需要p8权限、/oth/init还是需要p1、/test/init不设置权限,但是需要Token,我们的用户默认还是携带p1、p2权限,那么测试开始!



测试成功!那么到这里独立资源服鉴权JWT格式Token就完成了,那么上面既然提到JWT格式的Token是自包含,那么我们就解析JWT中的数据,这里还有一个问题,就是这个认证失败的返回,这里/mbb/init权限不够,返回的信息是SpringSecurity OAuth2默认的信息,不是很准确,对前端、用户不是很友好,那么这个将会在后面进行改造!归结为SpringSecurity OAuth2异常处理!

解析JWT自定义数据

过去写过一篇这个文章,介绍很详细,这里就不徒劳了,SpringSecurityOAuth2获取JWT中的数据

SpringSecurity OAuth2异常处理

由于SpringSecurity OAuth2异常处理这部分篇幅较长,单独拎出来写篇文章!SpringSecurity OAuth2异常处理OAuth2Exception

SpringSecurity OAuth2自定义授权模式(短信验证码授权)

之前写过一篇这个文章,在我写这篇文章的时候也是按照那篇文章复制粘贴过来的,直接可以用!SpringSecurityOauth2自定义授权模式

这篇文章还有一些相关联的,大家可以参看阅读一下!
SpringSecurityOAuth2授权流程源码分析(自定义验证码模式)
SpringSecurityOAuth2授权流程加载源码分析
SpringSecurityOAuth2授权流程源码分析

文章到这里那么就还剩打通数据库的操作了,本文之前都是没有打通数据库的,端点信息是写死在代码中的,然后用户信息也是写死在代码中的,那么接下来就完成主流程的最后一步,连接数据库!

持久化数据库

这里持久化数据库有两部分数据,一部分是端点数据,一部分是用户数据,我这里只写端点数据配置读取数据库,因为用户数据这块之前写过,这里也提一嘴,为什么我愿意在SpringSecurity这个框架上花这么多时间,其实是因为我写的第一篇博客其实就是关于SpringSecurity的,所以,从Springboot+Mybatis+Springsecurity+MySQL的整合这篇文章开始,到Spring Cloud整合SpringSecurity OAuth2(全网最强)这篇文章,我从只会简单接入SpringSecurity,到深入了解整个体系,深入源码,一路下来有点爽!哈哈哈

用户数据部分:
Springboot+Mybatis+Springsecurity+MySQL的整合
SpringSecurity从数据库获取用户信息

端点数据部分
这个其实特别简单了就直接贴代码了!前提是想导入数据库数!

	@Autowired(required = false)
    private DataSource dataSource;

	 @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            log.info("===>"+"基于数据库");
            clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

这种方式只能使用默认的表名,如果我们想修改表名也是可以的换写为下面的方案!

	@Autowired(required = false)
    private DataSource dataSource;
    
    String CLIENT_FIELDS = "client_id,  client_secret, resource_ids, scope, "
            + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
            + "refresh_token_validity, additional_information, autoapprove";

	 @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            log.info("===>"+"基于数据库");
            String selectClientDetailsSql = "select "+ CLIENT_FIELDS + " from 自定义表名where client_id = ?";
            String findClientDetailsSql ="select " + CLIENT_FIELDS + " from 自定义表名order by client_id ";
            JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
            jdbcClientDetailsService.setSelectClientDetailsSql(selectClientDetailsSql);
            jdbcClientDetailsService.setFindClientDetailsSql(findClientDetailsSql);
            clients.withClientDetails(jdbcClientDetailsService);
    }

这里还提供一种方案,就是我们可以将断点信息存放到缓存中,实际业务中端点信息其实便更并不频繁!那么我们可以使用如下方案!我们编写一个自己的ClientDetailsService继承JdbcClientDetailsService然后重写loadClientByClientId方法即可

创建CacheClientDetailsService

public class CacheClientDetailsServiceextends JdbcClientDetailsService {

	public CacheClientDetailsService(DataSource dataSource) {
		super(dataSource);
	}

	
	@Override
	@SneakyThrows
	@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
	public ClientDetails loadClientByClientId(String clientId) {
		return super.loadClientByClientId(clientId);
	}

}

在改造一下configure中的

 JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);

更改为

 CacheClientDetailsServiceextends jdbcClientDetailsService = new CacheClientDetailsServiceextends (dataSource);

即可!

那么至此主流程将全部完成,后续会出一篇我关于SpringSecurity 和SpringSecurity OAuth2 的文章合集!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/288336.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号