日常bb

日常bb

Spring Security 模块化(认证授权服务+资源服务)

2022-10-05
Spring Security 模块化(认证授权服务+资源服务)

Spring Security 模块化(认证授权服务+资源服务)

在微服务的架构下,对单体应用的认证授权服务和资源服务需要进行拆分和模块化处理。

该内容基于上一篇:实战单体应用(认证授权服务+资源服务)

项目结构

主要是将整个Spring Security OAuth2共用的配置写到security-common中,这样后期使用非常方便,只需要创建普通Spring Web项目,然后引用security-common模块即可。

SpringSecurity-demo
---auth-demo
---resources-demo
---security-common

SpringSecurity-demo Github地址

security-common

ResourceServerConfig

@Configuration
@EnableResourceServer
// 开启注解鉴权
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Bean
    @Primary
    public ResourceServerTokenServices tokenServices() {
        // 如果资源服务和认证服务在一起可以使用 DefaultTokenServices 进行本地校验
        // 使用远程服务请求授权服务器校验token,必须指定url,client_id,client_secret
        RemoteTokenServices services = new RemoteTokenServices();
        services.setCheckTokenEndpointUrl("http://127.0.0.1:3000/oauth/check_token");
        services.setClientId("rcbb");
        services.setClientSecret("rcbb");
        return services;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
                // 验证令牌的服务
                .tokenServices(tokenServices())
                .stateless(true);
    }

    //配置资源服安全规则
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                // 授权的请求
                .authorizeRequests()
                // 任何请求
                .anyRequest()
                // 需要身份认证
                .authenticated()
                .and().csrf().disable();
    }
}

并且使用Spring的自动装配,src/main/resources/META-INF/spring.factories配置。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  cc.rcbb.springsecurity.demo.common.config.ResourceServerConfig

auth-demo

认证服务的主要几个配置AuthorizationServerConfigTokenConfigWebSecurityConfig

TokenConfig

TokenConfig之前单体的时候没有单独配置,是因为默认就是使用内存存储令牌。单独拎出来是为了后续扩展。

@Configuration
public class TokenConfig {

    @Bean
    public TokenStore tokenStore() {
        // 使用内存存储令牌(普通令牌)
        return new InMemoryTokenStore();
    }

}

AuthorizationServerConfig

主要内容是开放授权服远程检验token
因为认证授权服服务和资源服务分开了,所以token无法共享,可以通过远程的方式校验Token

还有一种思路就是通过Redis,只要认证授权服服务和资源服务是使用的是同一个Redis,那么资源服务就可自己做校验,直接通过资源服务查询Redis

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private AuthenticationManager authenticationManager;


    //令牌管理服务
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service = new DefaultTokenServices();
        // 客户端详情服务
        service.setClientDetailsService(clientDetailsService);
        // 支持刷新令牌
        service.setSupportRefreshToken(true);
        // 令牌存储策略
        service.setTokenStore(tokenStore);
        // 令牌默认有效期2小时
        service.setAccessTokenValiditySeconds(7200);
        // 刷新令牌默认有效期3天
        service.setRefreshTokenValiditySeconds(259200);
        return service;
    }

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

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

    /**
     * 配置令牌的访问端点和令牌服务
     *
     * @param endpoints the endpoints configurer
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置认证管理器
        endpoints.authenticationManager(authenticationManager)
                .tokenServices(tokenService());
    }

    /**
     * @Description: 用来配置令牌端点(Token Endpoint)的安全约束
     * Token令牌默认是有签名的,并且资源服务需要验证这个签名,
     * 因此呢,你需要使用一个对称的Key值,用来参与签名计算,
     * 这个Key值存在于授权服务以及资源服务之中。
     * 或者你可以使用非对称加密算法来对Token进行签名,
     * Public Key公布在/oauth/token_key这个URL连接中,
     * 默认的访问安全规则是"denyAll()",
     * 即在默认的情况下它是关闭的,
     * 你可以注入一个标准的 SpEL 表达式到 AuthorizationServerSecurityConfigurer 这个配置中来将它开启
     * (例如使用"permitAll()"来开启可能比较合适,因为它是一个公共密钥)。
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 开放获取token key(这个是做JWT的时候开放的!也就是资源服可以通过这个请求得到JWT加密的key,目前这里没什么用,可以不用配置)
                .tokenKeyAccess("permitAll()")
                // 开放远程token检查
                .checkTokenAccess("permitAll()")
                // 允许client使用form的方式进行authentication的授权
                .allowFormAuthenticationForClients();
    }
}

resources-demo

基本上资源服务就没什么内容了,核心配置直接引入security-common模块即可。

现在就只剩资源UserController了。

@RestController
@RequestMapping("/user")
public class UserController {

    @PreAuthorize("hasAuthority('user_add')")
    @PostMapping
    public String save() {
        return "save() success";
    }

    @PreAuthorize("hasAuthority('user_remove')")
    @DeleteMapping
    public String remove() {
        return "remove() success";
    }

    @PreAuthorize("hasAuthority('user_update')")
    @PutMapping
    public String update() {
        return "update() success";
    }

    @Secured("ROLE_ADMIN")
    @GetMapping
    public String list() {
        return "list() success";
    }

}

测试

启动auth-demoresources-demo服务。

首先先通过账号密码获取token

通过账号密码获取token

然后再携带token访问资源服务,需要注意是资源服务是另外一个服务。

访问资源服务

测试成功。