Spring Cloud Security:Oauth2使用
1.OAuth2 简介
OAuth 2.0是用于授权的行业标准协议。OAuth 2.0为简化客户端开发提供了特定的授权流,包括Web应用、桌面应用、移动端应用等。
(1)OAuth2 相关名词解释
- Resource owner(资源拥有者):拥有该资源的最终用户,他有访问资源的账号密码;
- Resource server(资源服务器):拥有受保护资源的服务器,如果请求包含正确的访问令牌,可以访问资源;
- Client(客户端):访问资源的客户端,会使用访问令牌去获取资源服务器的资源,可以是浏览器、移动设备或者服务器;
- Authorization server(认证服务器):用于认证用户的服务器,如果客户端认证通过,发放访问资源服务器的令牌。
(2)四种授权模式
- Authorization Code(授权码模式):正宗的OAuth2的授权模式,客户端先将用户导向认证服务器,登录后获取授权码,然后进行授权,最后根据授权码获取访问令牌;
- Implicit(简化模式):和授权码模式相比,取消了获取授权码的过程,直接获取访问令牌;
- Resource Owner Password Credentials(密码模式):客户端直接向用户获取用户名和密码,之后向认证服务器获取访问令牌;
- Client Credentials(客户端模式):客户端直接通过客户端认证(比如client_id和client_secret)从认证服务器获取访问令牌。(本文采用的是这种模式)
(3)两种常用的授权模式
1.授权码模式
- (A)客户端将用户导向认证服务器;
- (B)用户在认证服务器进行登录并授权;
- ©认证服务器返回授权码给客户端;
- (D)客户端通过授权码和跳转地址向认证服务器获取访问令牌;
- (E)认证服务器发放访问令牌(有需要带上刷新令牌)。
2.密码模式
- (A)客户端从用户获取用户名和密码;
- (B)客户端通过用户的用户名和密码访问认证服务器;
- ©认证服务器返回访问令牌(有需要带上刷新令牌)。
启动这三个服务 api远程调用服务 auth 认证服务 gateway 网关服务
2.开始进行服务调试运行
2.1登录接口
http://localhost:9201/auth/oauth/token
首先源代码
我们进行改造。其实差不多的就是重写该方法,目的是我们将输出的数据封装到我们自定义返回类型中的data中去
@RestController
@RequestMapping("/oauth")
public class AuthController {
@Autowired
private TokenEndpoint tokenEndpoint;
private Set<HttpMethod> allowedRequestMethods;
/**
* Oauth2登录认证
*/
@RequestMapping(value = "/token", method = RequestMethod.POST)
public CommonResult<Oauth2TokenDto> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!this.allowedRequestMethods.contains(HttpMethod.POST)) {
return CommonResult.failed("请求方法必须是POST");
}
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder()
.token(oAuth2AccessToken.getValue())
.refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
.expiresIn(oAuth2AccessToken.getExpiresIn())
.tokenHead("Bearer ").build();
return CommonResult.success(oauth2TokenDto);
}
}
调用接口后:
2.2 oauth2调用接口的传参讲解,以下面登录接口为例子
先看下代码分析
- 当我们输错密码,再次调用登录认证接口时,发现认证失败的结果也统一了。
怎么弄的?上代码
看了源码,发现异常处理都是有这个类OAuth2Exception去处理,这时候我们自定义异常处理类去接受即可。
2.3自定义网关鉴权失败结果
- 当我们使用过期或签名不正确的JWT令牌访问需要权限的接口时,会直接返回状态码
401
;
首先看代码
打断点发现 这个格式都是自己自定义的
{
"code": 401,
"message": "暂未登录或token已经过期",
"data": "Jwt expired at 2022-07-27T08:47:51Z"
}
2.4兼容白名单接口
- 其实对于白名单接口一直有个问题,当携带过期或签名不正确的JWT令牌访问时,会直接返回token过期的结果,我们可以访问下登录认证接口试试;
- 明明就是个白名单接口,只不过携带的token不对就不让访问了,显然有点不合理。如何解决呢,我们先看看不带token访问怎么样;
- 其实我们只要在Oauth2默认的认证过滤器前面再加个过滤器,如果是白名单接口,直接移除认证头即可,首先定义好我们的过滤器;
/**
* 白名单路径访问时需要移除JWT请求头
* Created by hjt on 2020/7/24.
*/
@Component
public class IgnoreUrlsRemoveJwtFilter implements WebFilter {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
PathMatcher pathMatcher = new AntPathMatcher();
//白名单路径移除JWT请求头
List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
for (String ignoreUrl : ignoreUrls) {
if (pathMatcher.match(ignoreUrl, uri.getPath())) {
//请求不带Authorization
request = exchange.getRequest().mutate().header("Authorization", "").build();
exchange = exchange.mutate().request(request).build();
return chain.filter(exchange);
}
}
return chain.filter(exchange);
}
}
- 然后把这个过滤器配置到默认的认证过滤器之前即可,在ResourceServerConfig中进行配置;
/**
* 资源服务器配置
* Created by hjt on 2020/6/19.
*/
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
private final AuthorizationManager authorizationManager;
private final IgnoreUrlsConfig ignoreUrlsConfig;
private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
//自定义处理JWT请求头过期或签名错误的结果
http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
//对白名单路径,直接移除JWT请求头 必须设置在默认的认证过滤器前面
http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
//默认的认证过滤器
http.authorizeExchange()
.pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()//白名单配置
.anyExchange().access(authorizationManager)//鉴权管理器配置
.and().exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权
.authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证
.and().csrf().disable();
return http.build();
}
}