赞
踩
import cn.hutool.http.ContentType; import cn.hutool.http.HttpStatus; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.*; import lombok.experimental.Accessors; import javax.servlet.http.HttpServletResponse; import java.io.Serializable; import java.nio.charset.Charset; @Builder @ToString @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) @ApiModel(value = "响应信息主体") @JsonInclude(JsonInclude.Include.NON_NULL) public class R<T> implements Serializable { private static final long serialVersionUID = 1L; private static final String SUCCESS = "SUCCESS"; private static final String FAILED = "FAILED"; @Getter @Setter @ApiModelProperty(value = "返回标记:成功标记=200,失败标记=500") private int code; @Getter @Setter @ApiModelProperty(value = "返回信息") private String msg; @Getter @Setter @ApiModelProperty(value = "数据") private T data; public static <T> R<T> ok() { return restResult(null, HttpStatus.HTTP_OK, SUCCESS, true); } public static <T> R<T> ok(T data) { return restResult(data, HttpStatus.HTTP_OK, SUCCESS, true); } public static <T> R<T> ok(T data, String msg) { return restResult(data, HttpStatus.HTTP_OK, msg, true); } public static <T> R<T> ok(ResultCode result) { return restResult(null, result.code, result.msg, true); } public static <T> R<T> fail() { return restResult(null, HttpStatus.HTTP_INTERNAL_ERROR, FAILED, false); } public static <T> R<T> fail(String msg) { return restResult(null, HttpStatus.HTTP_INTERNAL_ERROR, msg, false); } public static <T> R<T> fail(T data) { return restResult(data, HttpStatus.HTTP_INTERNAL_ERROR, FAILED, false); } public static <T> R<T> fail(ResultCode result) { return restResult(null, result.code, result.msg, false); } public static <T> R<T> fail(int code, String msg) { return restResult(null, code, msg, false); } private static <T> R<T> restResult(T data, int code, String msg, boolean success) { R<T> apiResult = new R<>(); apiResult.setCode(code); apiResult.setData(data); apiResult.setMsg(msg); return apiResult; } public static void failRender(int code, String msg, HttpServletResponse response, int status) { try { ObjectMapper mapper = new ObjectMapper(); response.setContentType(ContentType.build(ContentType.JSON.getValue(), Charset.defaultCharset())); response.setStatus(status); response.getWriter().write(mapper.writeValueAsString(R.fail(code, msg))); } catch (Exception e) { e.printStackTrace(); } } public static void failRender(ResultCode resultCode, HttpServletResponse response, int status) { try { ObjectMapper mapper = new ObjectMapper(); response.setContentType(ContentType.build(ContentType.JSON.getValue(), Charset.defaultCharset())); response.setStatus(status); response.getWriter().write(mapper.writeValueAsString(R.fail(resultCode))); } catch (Exception e) { e.printStackTrace(); } } public static void cast(ResultCode resultCode) { throw new BusinessException(resultCode); } public static void cast(int code, String msg) { throw new BusinessException(code, msg); } }
@Slf4j
@Aspect
@Component
public class CustomOAuthTokenAspect {
@Around("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
public ResponseEntity response(ProceedingJoinPoint point) throws Throwable {
Object proceed = point.proceed();
ResponseEntity<OAuth2AccessToken> responseEntity = (ResponseEntity<OAuth2AccessToken>) proceed;
return ResponseEntity.ok(R.ok(responseEntity.getBody()));
}
}
import cn.mowen.common.result.R; import lombok.extern.slf4j.Slf4j; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; import org.springframework.security.web.util.ThrowableAnalyzer; import org.springframework.stereotype.Component; import org.springframework.web.HttpRequestMethodNotSupportedException; import java.io.IOException; @Slf4j @Component public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator { private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); @Override public ResponseEntity translate(Exception e) throws Exception { Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(e); Exception ase = (OAuth2Exception) this.throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain); //异常链中有OAuth2Exception异常 if (ase != null) { return this.handleOAuth2Exception((OAuth2Exception) ase); } //身份验证相关异常 ase = (AuthenticationException) this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase != null) { return this.handleOAuth2Exception(new CustomWebResponseExceptionTranslator.UnauthorizedException(e.getMessage(), e)); } //异常链中包含拒绝访问异常 ase = (AccessDeniedException) this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); if (ase instanceof AccessDeniedException) { return this.handleOAuth2Exception(new CustomWebResponseExceptionTranslator.ForbiddenException(ase.getMessage(), ase)); } //异常链中包含Http方法请求异常 ase = (HttpRequestMethodNotSupportedException) this.throwableAnalyzer.getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain); if (ase instanceof HttpRequestMethodNotSupportedException) { return this.handleOAuth2Exception(new CustomWebResponseExceptionTranslator.MethodNotAllowed(ase.getMessage(), ase)); } return this.handleOAuth2Exception(new CustomWebResponseExceptionTranslator.ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e)); } private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException { int status = e.getHttpErrorCode(); HttpHeaders headers = new HttpHeaders(); headers.setCacheControl(CacheControl.noCache()); headers.setPragma(CacheControl.noCache().getHeaderValue()); if (status == HttpStatus.UNAUTHORIZED.value() || e instanceof InsufficientScopeException) { headers.set(HttpHeaders.WWW_AUTHENTICATE, String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary())); } ResponseEntity<OAuth2Exception> response = new ResponseEntity(R.fail(e.getMessage()), headers, HttpStatus.valueOf(status)); return response; } private static class MethodNotAllowed extends OAuth2Exception { public MethodNotAllowed(String msg, Throwable t) { super(msg, t); } @Override public String getOAuth2ErrorCode() { return "method_not_allowed"; } @Override public int getHttpErrorCode() { return 405; } } private static class UnauthorizedException extends OAuth2Exception { public UnauthorizedException(String msg, Throwable t) { super(msg, t); } @Override public String getOAuth2ErrorCode() { return "unauthorized"; } @Override public int getHttpErrorCode() { return 401; } } private static class ServerErrorException extends OAuth2Exception { public ServerErrorException(String msg, Throwable t) { super(msg, t); } @Override public String getOAuth2ErrorCode() { return "server_error"; } @Override public int getHttpErrorCode() { return 500; } } private static class ForbiddenException extends OAuth2Exception { public ForbiddenException(String msg, Throwable t) { super(msg, t); } @Override public String getOAuth2ErrorCode() { return "access_denied"; } @Override public int getHttpErrorCode() { return 403; } } }
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter; import org.springframework.security.web.AuthenticationEntryPoint; public class CustomClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter { private final AuthorizationServerSecurityConfigurer configurer; private AuthenticationEntryPoint authenticationEntryPoint; public CustomClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer) { this.configurer = configurer; } @Override public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { this.authenticationEntryPoint = authenticationEntryPoint; } @Override protected AuthenticationManager getAuthenticationManager() { return configurer.and().getSharedObject(AuthenticationManager.class); } @Override public void afterPropertiesSet() { setAuthenticationFailureHandler((request, response, exception) -> authenticationEntryPoint.commence(request, response, exception)); setAuthenticationSuccessHandler((request, response, authentication) -> { // no-op - just allow filter chain to continue to token endpoint }); } }
import cn.hutool.http.HttpStatus; import cn.mowen.common.result.R; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Slf4j @Component public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { R.failRender(HttpStatus.HTTP_UNAUTHORIZED, exception.getMessage(), response, HttpStatus.HTTP_UNAUTHORIZED); log.error("Authentication异常: [{}], [{}], [{}]", request.getRequestURI(), exception.getMessage(), exception); } }
import cn.hutool.http.HttpStatus; import cn.mowen.common.result.R; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Slf4j @Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException, ServletException { R.failRender(HttpStatus.HTTP_UNAUTHORIZED, exception.getMessage(), response, HttpStatus.HTTP_UNAUTHORIZED); log.error("AccessDenied异常: [{}], [{}], [{}]", exception.getMessage(), exception.getLocalizedMessage(), exception.toString()); } }
@Configuration @EnableAuthorizationServer @AllArgsConstructor public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { /** * 用来配置令牌端点的安全约束, 密码校验方式等 */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { // 自定义客户端异常处理过滤器: {"error": "invalid_client", "error_description": "Bad client credentials"} CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security); endpointFilter.afterPropertiesSet();//初始化的时候执行 endpointFilter.setAuthenticationEntryPoint(customAuthenticationEntryPoint);//格式化客户端异常的响应格式 security //.allowFormAuthenticationForClients() .addTokenEndpointAuthenticationFilter(endpointFilter) //添加一个客户端认证之前的过滤器 ; /* * allowFormAuthenticationForClients 的作用: * 允许表单认证(申请令牌), 而不仅仅是Basic Auth方式提交, 且url中有client_id和client_secret的会走 ClientCredentialsTokenEndpointFilter 来保护, * 也就是在 BasicAuthenticationFilter 之前添加 ClientCredentialsTokenEndpointFilter,使用 ClientDetailsService 来进行 client 端登录的验证。 * 但是,在使用自定义的 CustomClientCredentialsTokenEndpointFilter 时, * 会导致 oauth2 仍然使用 allowFormAuthenticationForClients 中默认的 ClientCredentialsTokenEndpointFilter 进行过滤,致使我们的自定义 CustomClientCredentialsTokenEndpointFilter 不生效。 * 因此在使用 CustomClientCredentialsTokenEndpointFilter 时,不再需要开启 allowFormAuthenticationForClients() 功能。 */ } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.exceptionTranslator(customWebResponseExceptionTranslator)//自定义异常转换类(处理grant_type, username, password错误的异常) ; } }
import cn.mowen.common.constant.OauthConstant; import cn.mowen.common.constant.CommonWhiteConstant; import cn.mowen.common.exception.oauth.CustomAuthenticationEntryPoint; import cn.mowen.common.exception.oauth.CustomAccessDeniedHandler; import lombok.AllArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.TokenStore; @Configuration @EnableResourceServer @AllArgsConstructor public class ResourceServerConfig extends ResourceServerConfigurerAdapter { private final TokenStore jwtTokenStore; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId(OauthConstant.OAUTH_RESOURCE_ID) .tokenStore(jwtTokenStore) .authenticationEntryPoint(new CustomAuthenticationEntryPoint()) .accessDeniedHandler(new CustomAccessDeniedHandler()) .stateless(true) ; } @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() //放行 url 在此配置 .antMatchers(CommonWhiteConstant.white).permitAll() .antMatchers(white).permitAll() .anyRequest().authenticated() ; } // 白名单 private static final String[] white = { "/test/**" }; }
以下是同步到语雀的、可读性好一点,CSDN 继续看的点专栏就好。
Oauth2.0 核心篇
Oauth2.0 安全性(以微信授权登陆为例)
Oauth2.0 认证服务器搭建
Oauth2.0 添加验证码登陆方式
Oauth2.0 资源服务器搭建
Oauth2.0 自定义响应值以及异常处理
Oauth2.0 补充
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。