最新最简洁Spring Cloud Oauth2.0 Jwt 的Security方式


因为Spring Cloud 2020.0.0和Spring Boot2.4.1版本升级比较大,所以把我接入过程中的一些需要注意的地方告诉大家

我使用的版本是Spring boot 2.4.1+Spring Cloud 2020.0.0

我的架构是Gateway做Resource Server,然后服务内部不暴露到外网,微服务之间调用不需要再做验证。
而且为了减少请求Auth Server采用JWT方式

完全使用Spring Security的机制

最新的版本里面取消了org.springframework.cloud:spring-cloud-starter-oauth2
我使用了官方最新推荐集成Oauth2.0

AuthServer:
implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure'
implementation 'org.springframework.security:spring-security-oauth2-jose'
Gateway:
implementation 'org.springframework.security:spring-security-config'
implementation 'org.springframework.security:spring-security-oauth2-resource-server'
implementation 'org.springframework.security:spring-security-oauth2-jose'

取消Ribbon之后使用

implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'

下面把关键文件的源码粘贴出来
网关服务器
Gateway:

/**
 * 资源服务器配置
 * @author Mikey Huang
 * @date 2020/12/28
 */
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class ResourceServerConfig {
    /**
     * 注册安全验证规则
     * 配置方式与HttpSecurity基本一致
     */
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { //定义SecurityWebFilterChain对安全进行控制,使用ServerHttpSecurity构造过滤器链;
        http.authorizeExchange()
                //注意! hasrole里面的值必须和jwt负载的值一致
                .pathMatchers("/user/user/login").hasRole("ROLE_ADMIN")
                .pathMatchers("/user/user/hello").hasRole("ROLE_USER")
                .pathMatchers("/auth/oauth/token", "/auth/oauth/check_token").permitAll()
                .anyExchange().authenticated()
                .and().csrf().disable();
        http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
        return http.build();
    }

    /**
     * 使用ROLE来做权限验证,默认是SCOPE
     * @return
     */
    @Bean
    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
    }
}
server:
  port: 8080
spring:
  application:
    name: gateway
  security:
    oauth2:
      resourceserver:
        jwt:
          #配置RSA的公钥访问地址
          jwk-set-uri: 'http://localhost:8081/.well-known/jwks.json'
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: auth
          uri: lb://auth
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
        - id: user
          uri: lb://user
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix=1

授权服务器
AuthServer:

/**
 * 认证服务器配置
 * @author Mikey Huang
 * @date 2020/12/28
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    private final AuthenticationManager authenticationManager;
    private final JwtTokenEnhancer jwtTokenEnhancer;
    private final ApplicationContext context;

    public AuthorizationServerConfig(AuthenticationManager authenticationManager, JwtTokenEnhancer jwtTokenEnhancer,
                                     ApplicationContext context) {
        this.authenticationManager = authenticationManager;
        this.jwtTokenEnhancer = jwtTokenEnhancer;
        this.context = context;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 内存中创建一个客户端
        clients.inMemory()
                .withClient("client-app")
                .secret("123456")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("all");

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(accessTokenConverter());
        // 配置JWT的内容增强器
        enhancerChain.setTokenEnhancers(delegates);
        endpoints.authenticationManager(authenticationManager)
                .accessTokenConverter(accessTokenConverter())
                .tokenEnhancer(enhancerChain);

    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()");

    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setKeyPair(keyPair());
        return jwtAccessTokenConverter;
    }

    /**
     * 通过读取key store的配置构造
     * @return
     */
    @Bean
    public KeyPair keyPair(){
        AuthorizationServerProperties properties = authorizationServerProperties();
        Resource keyStore = context
                .getResource(properties.getJwt().getKeyStore());
        char[] keyStorePassword = properties.getJwt().getKeyStorePassword()
                .toCharArray();
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(keyStore,
                keyStorePassword);
        String keyAlias = properties.getJwt().getKeyAlias();
        char[] keyPassword = Optional
                .ofNullable(properties.getJwt().getKeyPassword())
                .map(String::toCharArray).orElse(keyStorePassword);
        return keyStoreKeyFactory.getKeyPair(keyAlias, keyPassword);
    }

    @Bean
    public AuthorizationServerProperties authorizationServerProperties() {
        return new AuthorizationServerProperties();
    }
}

/**
 * Spring Security配置
 * @author Mikey Huang
 * @date 2020/12/28
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/oauth/**", "/.well-known/jwks.json").permitAll()
                .anyRequest().authenticated().and().csrf().disable();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //内存中创建两个用户,两个不同的role用来测试权限
        auth.inMemoryAuthentication()
                .passwordEncoder(passwordEncoder())
                .withUser("mikey").password("123456").roles("ADMIN")
                .and()
                .withUser("sirius").password("654321").roles("USER");
    }

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

    @Bean
    public PasswordEncoder passwordEncoder() {
//        return new BCryptPasswordEncoder();
        // 方便测试,暂时不加密
        return NoOpPasswordEncoder.getInstance();
    }
}

/**
 * @author Mikey Huang
 * @date 2020/12/28
 */
@FrameworkEndpoint
public class JwkSetEndpoint {
    private final KeyPair keyPair;

    @Autowired
    public JwkSetEndpoint(KeyPair keyPair) {
        this.keyPair = keyPair;
    }

    @GetMapping("/.well-known/jwks.json")
    @ResponseBody
    public Map<String, Object> getKey() {
        RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic();
        RSAKey key = new RSAKey.Builder(publicKey).build();
        return new JWKSet(key).toJSONObject();
    }
}
/**
 * jwt token增强
 * @author Mikey Huang
 * @date 2020/12/28
 */
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        User user = (User) authentication.getPrincipal();
        Map<String, Object> info = new HashMap<>();
        //存入需要的信息,例如把密码设置到JWT中
        info.put("password", user.getPassword());
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}
server:
  port: 8081

spring:
  application:
    name: auth
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

security:
  oauth2:
    authorization:
      jwt:
        key-store: classpath:mikey.jks
        key-store-password: 123456
        key-alias: mikey
        key-password: 123456

测试截图
如果需要测试权限可以用创建的两个账号来分别调用User的两个接口,可以看到ROLE的效果



项目代码


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM