【Spring Boot】IDEA + Maven + Spring boot + OAuth2.0 认证授权

【项目需求】

需要对外提供一套关于订单操作的接口,因为旧系统框架,并不支持Restful风格,考虑到对外提供的一套接口肯定不能像内部使用一样,让人一看就看出了不规范,所以,独立出来了一个专门对外的api接口服务。既然对外,当然需要一定的限制,从而保证接口的安全性。从这方面考虑,这一服务的框架就决定使用Spring boot 集成OAuth2.0,使用的是客户端模式。

【框架搭建】

  • maven依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.1</version>
        </dependency>
    </dependencies>
  • 配置资源服务器
@Configuration
@EnableResourceServer
public class MyResourceServerConfiguration extends ResourceServerConfigurerAdapter {

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

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
                    // Since we want the protected resources to be accessible in the UI as well we need
                    // session creation to be allowed (it's disabled by default in 2.0.6)
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    .and()
                    .requestMatchers().anyRequest()
                    .and()
                    .anonymous()
                    .and()
                    .authorizeRequests()
                    //配置order访问控制,必须认证过后才可以访问
                    .antMatchers("/orders/**").authenticated();
            // @formatter:on
        }
}
  • 配置授权服务器
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private MyClientDetailsService myClientDetailsService;
    @Autowired
    private MyPasswordEncoder myPasswordEncoder;
    @Autowired
    private MyExceptionTranslator myExceptionTranslator;

    /**
     * 自定义客户端模式认证 MyClientDetailService
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(myClientDetailsService);
//        默认配置
//        clients.inMemory().withClient("client_1")
//                .resourceIds(DEMO_RESOURCE_ID)
//                .authorizedGrantTypes("client_credentials", "refresh_token")
//                .scopes("select")
//                .authorities("client")
//                .secret("123456");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints.exceptionTranslator(exceptionTranslator()).authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        //允许表单认证
        oauthServer.allowFormAuthenticationForClients().accessDeniedHandler(accessDeniedHandler()).passwordEncoder(myPasswordEncoder);
    }

    @Bean
    WebResponseExceptionTranslator exceptionTranslator() {
        return myExceptionTranslator;
    }

    @Bean
    AccessDeniedHandler accessDeniedHandler(){
        OAuth2AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler();
        accessDeniedHandler.setExceptionTranslator(myExceptionTranslator);
        accessDeniedHandler.setExceptionRenderer(new DefaultOAuth2ExceptionRenderer(){
            @Override
            public void handleHttpEntityResponse(HttpEntity<?> responseEntity, ServletWebRequest webRequest) throws Exception {
                super.handleHttpEntityResponse(responseEntity, webRequest);
                System.out.println("处理完成。。。");
            }
        });
        return accessDeniedHandler;
    }

}
  • 配置安全
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

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


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

}
  • 自定义验证(通过给对方提供的appKey和appSecret,验证是否允许访问系统接口)
@Component
public class MyClientDetailsService  implements ClientDetailsService {

    private static final String GOODS_RESOURCE_ID = "goods";

    @Autowired
    private AppSystemRepository appSystemRepository;

    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {

        AppSystem appSystem = appSystemRepository.getAppSystemByAppKey(clientId).get();
        BaseClientDetails details = new BaseClientDetails(appSystem.getAppKey(),GOODS_RESOURCE_ID,"select","client_credentials,refresh_token","client");
        details.setClientSecret(appSystem.getAppSecret());
        return details;
    }
}
  • 自定义异常处理
@Component
public class MyExceptionTranslator extends DefaultWebResponseExceptionTranslator {
    @Override
    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
        ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);

        System.out.println(responseEntity.getBody());

        return responseEntity;
    }
}

其他业务代码省略。完整代码已上传github:https://github.com/huzhiting/spring-boot-jpa-oauth2.0

【接口测试】

  • /oauth/token 获取token

在访问系统接口前,必须通过/oauth/token 获取token,授权模式为客户端,而获取token,需要带上允许访问系统的appKey和appSecret,appKey与appSecret对应不一致,会提示认证失败。

获取token失败:
这里写图片描述

获取token成功:
这里写图片描述

  • /orders 条件查询订单列表

在成功获取token后,即授权成功,那么再访问其他接口的时候,加上此token参数,才允许访问,否则会提示未授权。

查询订单列表失败:
这里写图片描述

查询订单列表成功:
这里写图片描述

【实战总结】

在之前,也用Spring boot 集成JWT做用户认证,感觉它们之间并没有什么可比性,但整个过程又有相似的体会,特意查了一下,对它们的解释:

JWT是一个“认证规范”,而OAuth是一个“开放标准网络协议”,并不是同一个类型的东西。

举个例子,你在服务器上存放了一些“资源”,JWT提供了一种用于发布接入令牌(Access Token),并对发布的签名接入令牌进行验证的方法,令牌(Token)本身包含了一系列声明,服务器可以根据这些声明限制用户对资源的访问。而你可以用这个token去访问你要访问的资源,而不需要每次都进行帐号密码认证。

而OAuth提供了一套详细的授权机制,你可以通过公开的或私有的设置,授权第三方应用访问特定资源。

上面的“你”指代用户。简单来说,JWT是用来证明你有权限访问特定资源,并提供一个安全方便的方式访问资源。而OAuth是你要授权第三方访问你的资源。

猜你喜欢

转载自blog.csdn.net/u013034223/article/details/82382375