搭建Spring Security OAuth2 Demo(授权码模式)

 


前言

搭建Spring Security OAuth2授权码模式demo,一方面在慢慢完善该文过程中做一个系统的总结,一方面做一个知识输出。


一、授权服务器搭建

  • 主要依赖
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.security.oauth.boot</groupId>
      <artifactId>spring-security-oauth2-autoconfigure</artifactId>
      <version>2.0.0.RELEASE</version>
</dependency>
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • SecurityConfig 
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private LoginFailureHandler failureHandler;

    @Autowired
    private UserDetailsService userDetailsService;

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**/*.js",
                        "/**/*.css",
                        "/toRegister",
                        "/logOut",
                        "/login"
                )
                .anonymous()
                .anyRequest().authenticated() //所有请求都需要通过认证
                .and().cors()
                .and()
//                .httpBasic() //Basic提交
                .formLogin()
                .loginPage("/login")
                .permitAll()
                // 登录失败
                .failureHandler(failureHandler)
                //关跨域保护
                .and()
                .csrf().disable();
    }
}
  • AuthorizationConfig
@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter
{

    @Autowired
    private DataSource dataSource;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * 配置令牌端点(Token Endpoint)的安全约束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer)
    {
        //允许表单提交
        oauthServer.allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()");
    }

    /**
     * 客户端配置
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception
    {
        RedisClientDetailsService clientDetailsService = new RedisClientDetailsService(dataSource);
        clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
        clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
        clients.withClientDetails(clientDetailsService);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
//                .exceptionTranslator(new CustomWebResponseExceptionTranslator())
                .tokenStore(tokenStore())
                .tokenServices(defaultTokenServices());
    }

    /**
     * 基于 Redis 实现,令牌保存到缓存
     */
    @Bean
    public TokenStore tokenStore()
    {
        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        tokenStore.setPrefix(CacheConstants.OAUTH_ACCESS);
        return tokenStore;
    }

    /**
     * 生成token的处理
     */
    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        // 是否支持 refreshToken
        tokenServices.setSupportRefreshToken(true);
        // 是否复用 refreshToken
        tokenServices.setReuseRefreshToken(true);
        // tokenServices.setTokenEnhancer(tokenEnhancer());
        // token有效期自定义设置,默认12小时,这里修改
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 15);
        //默认30天,这里修改
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;
    }

}

 

二、资源服务器搭建

  • ResourceServerConfig
@Configuration
@EnableResourceServer
@Order(0)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter
{

    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    @Autowired
    private CustomAuthExceptionEntryPoint exceptionEntryPoint;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private Environment environment;

    @Autowired
    private DefaultUserAuthenticationConverter defaultUserAuthenticationConverter;

    @Autowired
    private UserDetailsService userDetailsService;

    @Primary
    @Bean
    public RemoteTokenServices remoteTokenServices() {
        final RemoteTokenServices tokenServices = new RemoteTokenServices();
        //设置授权服务器check_token端点完整地址
        tokenServices.setCheckTokenEndpointUrl(environment.getProperty("security.oauth2.resource.token-info-uri"));
        //设置客户端id与secret,注意:client_secret值不能使用passwordEncoder加密!
        tokenServices.setClientId(environment.getProperty("security.oauth2.client.client-id"));
        tokenServices.setClientSecret(environment.getProperty("security.oauth2.client.client-secret"));
        //check_token后获取用户信息
        defaultUserAuthenticationConverter.setUserDetailsService(userDetailsService);
        DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
        defaultAccessTokenConverter.setUserTokenConverter(defaultUserAuthenticationConverter);
        tokenServices.setAccessTokenConverter(defaultAccessTokenConverter);
        return tokenServices;
    }

    /**
     * 设置token存储,这一点配置要与授权服务器相一致
     */
    @Bean
    public RedisTokenStore tokenStore(){
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        redisTokenStore.setPrefix("oauth:access:");
        return redisTokenStore;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //设置创建session策略
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        //@formatter:off
        //所有请求必须授权
        http.authorizeRequests()
                .anyRequest()
                .authenticated();
        //@formatter:on
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources)
    {
        resources.stateless(false);
        //设置token存储
        resources.tokenStore(tokenStore());
        resources.resourceId("resource1");
        resources.authenticationEntryPoint(exceptionEntryPoint).accessDeniedHandler(accessDeniedHandler);
        resources.tokenServices(remoteTokenServices());
    }
}
  • application.yml

auth-server: http://*.*.*.*:8082

security:
  oauth2:
    client:
      client-id: client-a # 授权服务器配置的client id
      client-secret: client-a-secret # 授权服务器配置的client secret
      scope: read_user_info
      #access-token-uri: ${auth-server}/oauth/token # 获取access token接口
      #user-authorization-uri: ${auth-server}/oauth/authorize #  获取Authorization Code接口
    resource:
      token-info-uri: ${auth-server}/oauth/check_token # 验证token的接口
#      user-info-uri: ${auth-server}/user # 一个可以获取认证授权的自定义接口,可以在授权服务器,也可以在其他服务器上
#      prefer-token-info: true # 如果同时配置了token-info-uri 和 user-info-uri,这个现象设置使用哪个取验证授权
  • SysUserController

/**
 * 登录验证
 *
 * @author demo
 */
@RestController
public class SysUserController {

    @Autowired
    private ISysUserService userService;

    /**
     * 获取用户信息
     *
     * @return 用户信息
     */
    @GetMapping("/getInfo/{userName}")
    public AjaxResult getInfo(@PathVariable(value = "userName", required = false) String userName) {
        SysUser user = userService.selectUserByUserName(userName);
        return AjaxResult.success(user);
    }

    @GetMapping("/user")
    public Authentication getUser() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

三、客户端接口文档

1)获取授权码

接口url

http://AUTHSERVER/oauth/authorize

请求方法

GET

输入参数

- client_id 客户端id

- redirect_uri 回调url 一定要与授权服务器配置保持一致,否则得不到授权码

- response_type code 授权码模式必须是code

- scope 作用域 与授权服务器配置保持一致

- state 加密串或状态标识(可选)

 

输出结果

回调客户端url,传入code

 

2授权码换令牌

接口url

http://AUTHSERVER/oauth/token

请求方法

POST

请求参数列表

- code 授权码

- grant_type authorization_code

- redirect_uri 回调url

- scope作用域

请求头列表

- Authorization:Basic 经Base64加密后的username:password的字符串

 

输出参数

{"access_token":"c4dbd362-4354-4434-a9ba-a998eca55bcb",

"token_type":"bearer","expires_in":43199,"scope":"read_user_info"}

3)使用token访问受保护的资源

接口url

http://RESOURCESERVER/getUser/{userName}

 

请求方法

GET

请求头列表

 

Authorization:Bearer 令牌值  这里要注意的是Bearer与token间有一个空格

输出参数

用户信息json串


总结

主要配置如上,后续还会继续补充细节,若有错误,期待指正,谢谢!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值