Spring security oauth2 包含以下两个endpoint来实现Authorization Server:

AuthorizationEndpoint: 授权请求访问端点, 默认url: /oauth/authorize

TokenEndpoint: 获取access token的请求的访问端点, 默认url: /oauth/token

添加依赖

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

配置Authorization Server

@EnableAuthprizationServer注解, 配合实现了AuthorizationServerConfigurer的配置类来配置Authorization Server。

AuthorizationServerConfigurer中主要的配置有三个, 覆盖对应的方法即可:

ClientDetailsServiceConfigurer:

定义了client detail service, 可以简单的指定几个client, 或者指向数据库中的表。

可以存储client信息到内存中或数据库中, 即in-memory和JDBC两种实现。

AuthorizationServerSecurityConfigurer:

定义了token端点的安全策略

其中有两个重要的端点:

/oauth/check_token: 检查token的有效性

/oauth/token_key: 获取token的key

AuthorizationServerEndpointsConfigurer:

定义authorization endpoint, token endpoint以及token service。token service管理token相关的一切, 除了token的持久化是委托给TokenStore来实现, 默认的实现是DefaultTokenServices.

有三种类型的TokenStore:

InMemoryTokenStore: 默认实现, 存储在内存中。

JdbcTokenStore: token数据存储在关系型数据库中。

JWT 类型的TokenStore, 典型的是JwtTokenStore, 不存储到任何地方, 把所有的数据编码到token中, 因此token的撤回(revoke)实现起来优点麻烦。

配置:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
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.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.util.Arrays;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

  @Value("${security.oauth2.client.client-id}")
  private String clientId;

  @Value("${security.oauth2.client.client-secret}")
  private String clientCredentials;

  private final PasswordEncoder passwordEncoder;

  private final AuthenticationManager authenticationManager;

  private final DefaultTokenEnhancer defaultTokenEnhancer;
  
  private final UserDetailsService userDetailsService;

  @Autowired
  public AuthorizationServerConfig(PasswordEncoder passwordEncoder, AuthenticationManager authenticationManager,
                                   DefaultTokenEnhancer defaultTokenEnhancer,
                                   UserDetailsService userDetailsService) {
    this.passwordEncoder = passwordEncoder;
    this.authenticationManager = authenticationManager;
    this.defaultTokenEnhancer = defaultTokenEnhancer;
    this.userDetailsService = userDetailsService;
  }

  public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
    configurer
        .inMemory()
        .withClient(clientId)
        .secret(passwordEncoder.encode(clientCredentials))
        .authorizedGrantTypes("password", "refresh_token")
        .accessTokenValiditySeconds(3600);
  }

  @Override
  public void configure(final AuthorizationServerSecurityConfigurer oauthServer) {
    oauthServer.tokenKeyAccess("permitAll()")
        .checkTokenAccess("isAuthenticated()")
        .allowFormAuthenticationForClients(); // 默认通过basic authentication, 即加一个header为Authorization: Basic base64(username:password), 指定这个可以设置form提交, 把client-id, client-secret放到post参数中。
  }

  @Bean
  TokenStore tokenStore() {
    return new JwtTokenStore(jwtAccessTokenConverter());
  }

  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
    jwtConverter.setSigningKey("your-sign-key");
    return jwtConverter;
  }

  @Bean
  public WebResponseExceptionTranslator loggingExceptionTranslator() {
    return new DefaultWebResponseExceptionTranslator() {
      @Override
      public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
        e.printStackTrace();
        ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
        HttpHeaders headers = new HttpHeaders();
        headers.setAll(responseEntity.getHeaders().toSingleValueMap());
        OAuth2Exception excBody = responseEntity.getBody();
        return new ResponseEntity<>(excBody, headers, responseEntity.getStatusCode());
      }
    };
  }

  public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(Arrays.asList(defaultTokenEnhancer, jwtAccessTokenConverter()));

    endpoints.authenticationManager(authenticationManager)
    .userDetailsService(userDetailsService)
    .tokenStore(tokenStore())
    .tokenEnhancer(tokenEnhancerChain)
    .exceptionTranslator(loggingExceptionTranslator());
  }
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(10);
  }

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

  protected void configure(HttpSecurity http) throws Exception {
    http
        .formLogin().permitAll()
        .and()
        .csrf().disable()
        .cors().disable()
        .authorizeRequests()
        .antMatchers(HttpMethod.OPTIONS).permitAll()
        .antMatchers("/oauth/**").permitAll()
        .anyRequest().authenticated();
  }
}

formLogin().permitAll()对password flow不是必须的, 只对implicit flow有影响。

当时测试implicit flow的时候确实是这样, 在访问/oauth/authorize之后, 需要登录, 登录后一直提示错误, 但没有错误信息, 从而获取不到token, 加了formLogin().permitAll()选项后就正常返回token了。

获取access token:

访问地址: POST localhost:8080/oauth/token

参数:client_id=clientId&client_secret=clientSecret&grant_type=password&username=username&password=password

刷新access token:

POST localhost:8080/oauth/token

参数:

client_id=clientId&client_secret=clientSecret&grant_type=refresh_token&scope=any&refresh_token=refreshToken

顺便介绍一下其他的grant type:

grant type授权类型一般有client credentials, password, implicit, authorization code, refresh。

client credentials是直接访问/oauth/token端点带grant_type

password 访问/oauth/token端点带grant_type

implicit 访问/oauth/authorize端点带response_type参数, 值为token, 然后会直接重定向到你指定的redirect_uri, url中带access_token参数

authorize code 先访问/oauth/authorize端点带response_type参数, 值为code, 然后它会返回一个code参数; 带上code参数访问/oauth/token端点获取access token。

refer:

https://www.baeldung.com/rest-api-spring-oauth2-angular

https://dzone.com/articles/spring-boot-oauth2-getting-the-authorization-code

https://walkingtree.tech/securing-microservices-oauth2/

https://projects.spring.io/spring-security-oauth/docs/oauth2.html

posted on 2019-06-24 19:50  浮舟z  阅读(474)  评论(0编辑  收藏  举报