SpringCloud-SpringSecurity+Oauth2:搭建资源服务

前言

之前写过使用springsecurity搭建认证服务SpringSecurity+Oauth2:密码授权模式获取Token(MongoDB+JWT),这篇则写关于如何搭建资源服务,只有验证通过的token才能访问。

操作

1、配置Pom.xml引用spring-cloud-securityoauth2的jar包
2、配置主类@EnableResourceServer注解,开启资源服务
3、创建JWTTokenStoreConfig类,配置和解析token
4、创建ResourceServerConfiguration类配置访问权限以及自定义异常
5、自定义springsecurity异常信息(注意:认证和资源服务的自定义异常是统一的没有区别,下面会说明)

1、配置Pom.xml引用spring-cloud-securityoauth2的jar包


    org.springframework.cloud
    spring-cloud-starter-oauth2
    2.2.5.RELEASE


    org.springframework.cloud
    spring-cloud-security
    2.2.5.RELEASE

2、配置主类@EnableResourceServer注解,开启资源服务

@SpringBootApplication
// 资源保护服务
@EnableResourceServer
// 服务发现
@EnableDiscoveryClient
@EnableFeignClients
@RefreshScope
public class Xxxx {
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    public static void main(String[] args) {
        SpringApplication.run(Xxxx.class, args);
    }
}

3、创建JWTTokenStoreConfig类,配置和解析token

@Configuration
public class JWTTokenStoreConfig {

    @Autowired
    private ServiceConfig serviceConfig;

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

    //JWT
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }


    //JWT
    // 从配置文件中获取jwt key,然后自己解析token是否有效,
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(serviceConfig.getJwtSigningKey());
        return converter;
    }

}

4、创建ResourceServerConfiguration类配置访问权限以及自定义异常

@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {

        OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
        authenticationEntryPoint.setExceptionTranslator(new CustomOAuthWebResponseExceptionTranslator());
        resources.authenticationEntryPoint(authenticationEntryPoint);

        OAuth2AccessDeniedHandler oAuth2AccessDeniedHandler = new OAuth2AccessDeniedHandler();
        oAuth2AccessDeniedHandler.setExceptionTranslator(new CustomOAuthWebResponseExceptionTranslator());
        resources.accessDeniedHandler(oAuth2AccessDeniedHandler);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 放开/sendRegisterEmailMessage,不需要校验token
                .antMatchers("/websocket/**").permitAll()
                .antMatchers("/info/**").permitAll()
                .anyRequest().authenticated();
    }
}

5、自定义springsecurity异常信息

创建CustomOAuthException

@JsonSerialize(using = CustomOAuthExceptionSerializer.class)
public class CustomOAuthException extends OAuth2Exception {
    private static Logger log = LoggerFactory.getLogger(CustomOAuthException.class);

    private String oAuth2ErrorCode;

    private int httpErrorCode;

    public CustomOAuthException(String msg, String oAuth2ErrorCode, int httpErrorCode) {
        super(msg);
        this.oAuth2ErrorCode = oAuth2ErrorCode;
        this.httpErrorCode = httpErrorCode;
    }

}

创建CustomOAuthExceptionSerializer

public class CustomOAuthExceptionSerializer extends StdSerializer {
    private static Logger log = LoggerFactory.getLogger(CustomOAuthExceptionSerializer.class);

    protected CustomOAuthExceptionSerializer() {
        super(CustomOAuthException.class);
    }

    @Override
    public void serialize(CustomOAuthException e, JsonGenerator generator, SerializerProvider serializerProvider) throws IOException {
        generator.writeStartObject();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        generator.writeObjectField("error",e.getOAuth2ErrorCode());
        generator.writeObjectField("status", e.getHttpErrorCode());
        generator.writeObjectField("message", e.getMessage());
        generator.writeObjectField("path", request.getServletPath());
        generator.writeObjectField("timestamp", (new Date()).getTime());
        if (e.getAdditionalInformation() != null) {
            for (Map.Entry entry : e.getAdditionalInformation().entrySet()) {
                String key = entry.getKey();
                String add = entry.getValue();
                generator.writeObjectField(key, add);
            }
        }
        generator.writeEndObject();
    }
}

创建CustomOAuthWebResponseExceptionTranslator

@Component
public class CustomOAuthWebResponseExceptionTranslator extends DefaultWebResponseExceptionTranslator {
    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
    private static Logger log = LoggerFactory.getLogger(CustomOAuthWebResponseExceptionTranslator.class);

    @Override
    public ResponseEntity translate(Exception e) throws Exception {

        ResponseEntity translate = super.translate(e);
        OAuth2Exception body = translate.getBody();

        CustomOAuthException customOauthException = new CustomOAuthException(body.getMessage(),body.getOAuth2ErrorCode(),body.getHttpErrorCode());
        ResponseEntity response = new ResponseEntity<>(customOauthException, translate.getHeaders(), translate.getStatusCode());
        return response;
    }
}

最后如果用户名或密码错误则返回下面的错误信息:

{
  timestamp: 1658149719489,
  path: '/oauth/token',
  error: 'invalid_request',
  message: '用户名或密码错误'
  status: 400
}

如果token错误则返回下面的错误信息:

{
  timestamp: 1658149719489,
  path: '/oauth/token',
  error: 'invalid_request',
  message: 'token错误或过期'
  status: 400
}

如果refresh_token错误则返回下面的错误信息:

{
  timestamp: 1658149719489,
  path: '/oauth/token',
  error: 'invalid_request',
  message: 'refresh_token错误或过期'
  status: 400
}

资源服务的目录结构
image.png

认证服务的目录结构
image.png

总结

1、springsecurity 自带的异常信息不符合自己的需求的时候就要自定义返回错误信息
2、错误信息返回的错误码如果是401那么照样返回401,如果觉得401不太好可以自行设置

引用

SpringBoot Security的自定义异常

你可能感兴趣的:(SpringCloud-SpringSecurity+Oauth2:搭建资源服务)