最新最簡潔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