Spring Security:二、整合OAuth 2.0(新)

  • Post author:
  • Post category:其他


项目结构:

代码地址:

一、什么是OAuth 2.0


什么是OAuth 2.0


OAuth 2.0的四种模式


OAuth2.0图解




OAuth 2.0客服端模式有A、B两台服务器,这里在一个服务中模拟


二、说明

2.1、spring security oauth2建立在spring security基础之上

2.2、

  • 配置spring security
  • 配置认证服务器
  • 配置资源服务器

2.3、oauth2根据使用场景不同,分成了4种模式,还可以自定义模式

2.4、这里主要讲密码模式(以下简称password模式)和客户端模式(以下简称client模式),授权码主要是qq、微信等大平台才会使用到

三、开始整合,看代码

我们的目标是保护资源,控制权限

3.1、创建springboot

3.2、导包

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.3.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.5.RELEASE</version>
        </dependency>
        <!-- 将token存储在redis中 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

3.3、创建controller

@RestController
public class HelloController {

    /**
     * 不需要token
     * @param id
     * @return
     */
    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable String id) {
        //效验权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return "product id : " + id;
    }

    /**
     * 需要token
     * @param id
     * @return
     */
    @GetMapping("/order/{id}")
    public String getOrder(@PathVariable String id) {
        //效验权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return "order id : " + id;
    }

}

3.4、配置资源服务器

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    private static final String DEMO_RESOURCE_ID = "order";

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            // 因为我们希望受保护的资源也能在UI中被访问,所以我们需要允许会话创建(在2.0.6中默认禁用)
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            .and()
            .requestMatchers().anyRequest()
            .and()
            .anonymous()
            .and()
            .authorizeRequests()
//                    .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_USER')")
            .antMatchers("/order/**").authenticated();//配置order访问控制,必须认证过后才可以访问
    }
}

3.5、配置授权服务器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    private static final String DEMO_RESOURCE_ID = "order";

    @Autowired(required=false)
    AuthenticationManager authenticationManager;

    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //配置两个客户端,client(客户端)认证,password认证
        clients.inMemory().withClient("client_1")
                .resourceIds(DEMO_RESOURCE_ID)
                .authorizedGrantTypes("client_credentials", "refresh_token")
                .scopes("select")
                .authorities("client")
                .secret("123456")
                .and().withClient("client_2")
                .resourceIds(DEMO_RESOURCE_ID)
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("select")
                .authorities("client")
                .secret("123456");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(new RedisTokenStore(redisConnectionFactory))
                .authenticationManager(authenticationManager);
    }


    /**
     *  配置:安全检查流程,用来配置令牌端点(Token Endpoint)的安全与权限访问
     *  默认过滤器:BasicAuthenticationFilter
     *  1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
     *  2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值:denyAll()
     * 对以下的几个端点进行权限配置:
     * /oauth/authorize:授权端点
     * /oauth/token:令牌端点
     * /oauth/confirm_access:用户确认授权提交端点
     * /oauth/error:授权服务错误信息端点
     * /oauth/check_token:用于资源服务访问的令牌解析端点
     * /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
     **/
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        //允许表单认证
        oauthServer.allowFormAuthenticationForClients();
    }

}

3.6、配置spring security(实际项目中,一般数据库保存用户)

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * MyPasswordEncoder类自定义密码匹配规则
     * @return
     */
    /*@Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }*/

    @Bean
    @Override
    protected UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("adminUser").password("adminUser").authorities("adminRole").build());
        manager.createUser(User.withUsername("testUser").password("testUser").authorities("testRole").build());
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
                .requestMatchers().anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/*").permitAll();
        // @formatter:on
    }

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

}

3.7、自定义密码验证

@Component
public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    /**
     * 密码效验
     * @param charSequence
     * @param s
     * @return
     */
    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(charSequence.toString());
    }
}

3.8、启动程序

四、测试

4.1、password模式:

http://localhost:8080/oauth/token?username=adminUser&password=adminUser&grant_type=password&scope=select&client_id=client_2&client_secret=123456

4.2、client模式:

http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client_1&client_secret=123456

4.3、依次访问以下url

不需要token:

http://localhost:8080/product/1

需要token(访问失败):

http://localhost:8080/order/1

需要token(访问成功):

http://localhost:8080/order/1?access_token=56465b41-429d-436c-ad8d-613d476ff322


初次整合结束

其它入门文档:

1、

理解OAuth 2.0 阮一峰

2、

Re:从零开始的Spring Security Oauth2(一)

3、

Spring Security 与 OAuth2(完整案例)

4、

Spring Security 解析(五) —— Spring Security Oauth2 开发

5、

Spring Security OAuth2.0分布式认证和授权方案

【精】


异常处理:


1.1、IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”

解决方案:添加 MyPasswordEncoder 类(注意要添加 @Component 注解,启动程序的时候最好用idea原生的,我就在JRebel中吃了亏)

@Component 
public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(charSequence.toString());
    }
}

重新请求:

http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client_1&client_secret=123456


1.2、{“error”:”unsupported_grant_type”,”error_description”:”Unsupported grant type: password”}

解决方案:意思是没有定义password的模式,需要在 WebSecurityConfigurerAdapter 的实现类中添加以下代码

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

从新请求:

http://localhost:8080/oauth/token?username=user_1&password=123456&grant_type=password&scope=select&client_id=client_2&client_secret=123456


1.3、oauth2验证中WebSecurityConfigurerAdapter和ResourceServerConfigurerAdapter的区别

对于刚入门的来说要好好理解以下以下三个继承类的作用

WebSecurityConfigurerAdapter、ResourceServerConfigurerAdapter、AuthorizationServerConfigurerAdapter

WebSecurityConfigurerAdapter默认情况下是springsecurity的http配置

ResourceServerConfigurerAdapter默认情况下是spring security oauth2的http配置

但是ResourceServerConfigurerAdapter的默认order是大于100的,意思是WebSecurityConfigurerAdapter的拦截顺序要先于ResourceServerConfigurerAdapter,我们想要使用oauth的http配置,就需要在相应的配置文件(yml)配置security.oauth2.resource.filter-order = 3

这样就让ResourceServerConfigurerAdapter的生效



版权声明:本文为qq_16946803原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。