Zuul + Oauth 2 实现鉴权功能
Published in:2023-05-31 |
Words: 3.4k | Reading time: 19min | reading:

Zuul + Oauth 2 实现鉴权功能

技术简介

oauth2

OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This specification and its extensions are being developed within the IETF OAuth Working Group.

OAuth 2.1 is an in-progress effort to consolidate OAuth 2.0 and many common extensions under a new name.

zuul

Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate.

技术版本选择

  • 1.zuul 版本选择
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
  • 2.oauth2 版本选择
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>

实现

  • 1.网关搭建

    见:<<Swagger + Zuul 整合微服务接口文档>> 一文

  • 2.oauth 配置
    • 在 application.yml 文件中配置 auth 服务
1
2
3
4
5
6
7
8
9
10
11
12
auth:
tokenValiditySeconds: 1200 #token存储到redis的过期时间
clientId: domain
clientSecret: domain
cookieDomain: domain.com
cookieMaxAge: -1
encrypt:
key-store:
location: classpath:rs.keystore
secret: domainkeystore
alias: domainkey
password: domain

security 配置,放行登陆、获取验证码等接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.czq.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@Order(-1)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/userlogin","/userlogout","/userjwt");

}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
//采用bcrypt对密码进行编码
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().and()
.formLogin()
.and()
.authorizeRequests().anyRequest().permitAll();

}
}

继承 AuthorizationServerConfigurer 类配置 auth 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.czq.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;


@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
//jwt令牌转换器
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
UserDetailsService userDetailsService;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
TokenStore tokenStore;
@Autowired
private CustomUserAuthenticationConverter customUserAuthenticationConverter;

//读取密钥的配置
@Bean("keyProp")
public KeyProperties keyProperties(){
return new KeyProperties();
}

@Resource(name = "keyProp")
private KeyProperties keyProperties;


//客户端配置
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(this.dataSource).clients(this.clientDetails());
}

@Bean
@Autowired
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory
(keyProperties.getKeyStore().getLocation(), keyProperties.getKeyStore().getSecret().toCharArray())
.getKeyPair(keyProperties.getKeyStore().getAlias(),keyProperties.getKeyStore().getPassword().toCharArray());
converter.setKeyPair(keyPair);
//配置自定义的CustomUserAuthenticationConverter
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
return converter;
}
//授权服务器端点配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter)
.authenticationManager(authenticationManager)//认证管理器
.tokenStore(tokenStore)//令牌存储
.userDetailsService(userDetailsService);//用户信息service
}

//授权服务器的安全配置
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
// oauthServer.checkTokenAccess("isAuthenticated()");//校验token需要认证通过,可采用http basic认证
oauthServer.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}

// 采用 bcrypt对密码进行hash
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}


}

配置自定义的 CustomUserAuthenticationConverter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.czq.auth.config;

import com.czq.auth.service.UserJwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.Map;

@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Autowired
UserDetailsService userDetailsService;

@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
LinkedHashMap response = new LinkedHashMap();
String name = authentication.getName();
response.put("username", name);

Object principal = authentication.getPrincipal();
UserJwt userJwt = null;
if(principal instanceof UserJwt){
userJwt = (UserJwt) principal;
}else{
//refresh_token默认不去调用userdetailService获取用户信息,这里我们手动去调用,得到 UserJwt
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
userJwt = (UserJwt) userDetails;
}
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}

return response;
}
}
  • 3.Oauth 令牌生成

    在用户登录时申请 token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
 /**
* description: 认证方法 从 spring security去获取认证是否成功信息 即是否能成功获取令牌
* @param username 用户名
* @param password 密码
* @param clientId 客户id
* @param clientSecret 客户密码
* @return
* @throws
* @author czq
* @date ${date} ${time}
*/
private AuthToken applyToken(String username, String password, String clientId, String clientSecret) throws UnsupportedEncodingException {
// 选中认证服务地址
ServiceInstance serviceInstance = loadBalancerClient.choose(ServiceList.AUTH_SERVICE);
if (serviceInstance == null){
LOGGER.error("choose an auth instance null");
ExceptionCast.cast(AuthCode.AUTH_LOGIN_AUTHSERVER_NOTFOUND);
}
// 获取令牌url
URI uri = serviceInstance.getUri();
String authUrl = uri+ "/auth/oauth/token";
// 定义body
MultiValueMap<String,String> formData = new LinkedMultiValueMap<>();
// 授权方式
formData.add("grant_type","password");
formData.add("username",username);
formData.add("password",password);
// 定义头部
MultiValueMap<String,String> header = new LinkedMultiValueMap<>();
header.add("Authorization",httpbasic(clientId,clientSecret));
///指定 restTemplate当遇到400或401响应时候也不要抛出异常,也要正常返回值
restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
@Override
public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
//当响应的值为400或401时候也要正常响应,不要抛出异常
if(response.getRawStatusCode()!=400 && response.getRawStatusCode()!=401){
super.handleError(response);
}
}
});
Map map = null;
try {
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(formData, header);
// http请求spring security 申请令牌接口
ResponseEntity<Map> mapResponseEntity = restTemplate.exchange(authUrl, HttpMethod.POST,
httpEntity, Map.class);
map = mapResponseEntity.getBody();
} catch (RestClientException e) {
e.printStackTrace();
LOGGER.error("request oauth_token_password error: {}",e.getMessage());
e.printStackTrace();
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
if (map == null ||
map.get("access_token") == null ||
map.get("refresh_token") == null ||
map.get("jti") == null){//jti是jwt令牌的唯一标识作为用户身份令牌
String error_description = (String) map.get("error_description");
if (StringUtils.isNotEmpty(error_description)) {
if(error_description.equals("坏的凭证")){
ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
} else if (error_description.indexOf("UserDetailsService returned null") > 0){
ExceptionCast.cast(AuthCode.AUTH_ACCOUNT_NOTEXISTS);
}
}
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
AuthToken authToken = new AuthToken();
// 访问令牌jwt
String jwt_token = (String) map.get("access_token");
// 刷新令牌jwt
String refresh_token = (String) map.get("refresh_token");
//jti,作为用户的身份标识
String access_token = (String) map.get("jti");
authToken.setJwt_token(jwt_token);
authToken.setAccess_token(access_token);
authToken.setRefresh_token(refresh_token);
return authToken;

在获取到令牌后,存储至 redis 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
   /**
* description: 登录服务 认证方法
* @param username 用户名
* @param password 密码
* @param clientId 客户端id
* @param clientSecret 客户端密码
* @return token
* @throws
* @author czq
* @date ${date} ${time}
*/
public AuthToken login(String username, String password, String clientId, String clientSecret) throws UnsupportedEncodingException {
// 申请令牌
AuthToken authToken = applyToken(username,password,clientId,clientSecret);
if (authToken == null ){
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
String access_token = authToken.getAccess_token();
String content = (String) JSON.toJSONString(authToken);
boolean saveTokenResult = saveToken(access_token,content,tokenValiditySeconds);
if (!saveTokenResult){
ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
}
return authToken;
}
/**
* description: 存储令牌到redis
* @param access_token 登录token
* @param content 内容
* @param ttl 过期时间
* @return bool
* @throws
* @author czq
* @date ${date} ${time}
*/
private boolean saveToken(String access_token, String content, long ttl) {
// 令牌名
String key = "user_token:" + access_token;
// 保存令牌到redis
stringRedisTemplate.boundValueOps(key).set(content,ttl, TimeUnit.SECONDS);
// 获取过期时间
Long expire = stringRedisTemplate.getExpire(key,TimeUnit.SECONDS);
return expire > 0;
}

保存到 redis 后,通过 cookie 返回至前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* description: 将令牌保存到cookie
* @param access_token token
* @return
* @throws
* @author czq
* @date ${date} ${time}
*/
private void saveCookie(String access_token) {
HttpServletResponse response = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getResponse();
// 添加cookie令牌 最后一个参数设置为false 表示允许浏览器获取
assert response != null;
CookieUtil.addCookie(response,cookieDomain,"/",
"uid",access_token,cookieMaxAge,false);
}

最终返回 token 至前端,前端通过携带 cookie 获取 jwt 令牌

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* description: 从redis查询令牌
* @param access_token 准入token
* @return auth token
* @throws
* @author czq
* @date ${date} ${time}
*/
public AuthToken getUserToken(String access_token) {
String userToken = "user_token:" + access_token;// String name = "user_token:" + access_token;
String userTokenString = stringRedisTemplate.opsForValue().get(userToken);
if (userToken != null){
AuthToken authToken = null;
try {
authToken = JSON.parseObject(userTokenString,AuthToken.class);
}catch (Exception e){
LOGGER.error("getUserToken from redis and execute JSON.parseObject error {}",e.getMessage());
e.printStackTrace();
}
return authToken;
}
return null;
}
  • 4.网关配置过滤规则,放行登录等接口

    在过滤规则中,对不符合要求接口予以拦截并返回 403。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package com.czq.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.czq.common.model.response.CommonCode;
import com.czq.common.model.response.ResponseResult;
import com.czq.model.auth.ext.*;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.PathMatcher;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Slf4j
@Component
public class LoginFilter extends ZuulFilter {
List<String> paths = new ArrayList<>();

private static final Logger LOG = LoggerFactory.getLogger(LoginFilter.class);

// @Autowired
// private RestTemplate restTemplate;

public LoginFilter() {
super();
// paths.add("/login/logining");
paths.add("/**/auth/oauth/check_token");
paths.add("/**/auth/oauth/token");
paths.add("/**/auth/userlogin");
paths.add("/**/auth/userjwt");
paths.add("/**/manage/captcha");
paths.add("/**/manage/verification");
paths.add("/ui/**");
paths.add("/**/swagger**/**");
paths.add("/**/v2/api-docs");
paths.add("/**/*.css");
paths.add("/**/*.jpg");
paths.add("/**/*.png");
paths.add("/**/*.gif");
paths.add("/**/*.js");
paths.add("/**/*.svg");

}

// 过滤器类型
/*
* pre 请求前调用
* routing 在路由请求前调用
* post在routing error过滤器后调用
* error处理请求发生错误调用
* */
@Override
public String filterType() {
return "pre";
}

@Override
public int filterOrder() {
return 1;////int值来定义过滤器的执行顺序,数值越小优先级越高
}

@Override
public boolean shouldFilter() {//执行过滤器
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String uri = request.getRequestURI();
PathMatcher matcher = new AntPathMatcher();
Optional<String> optional = paths.stream().filter(t -> matcher.match(t, uri)).findFirst();
return !optional.isPresent();
}

@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletResponse httpServletResponse = requestContext.getResponse();
HttpServletRequest httpServletRequest = requestContext.getRequest();
//取出头部信息Authorization
String authorization = httpServletRequest.getHeader("Authorization");
if (StringUtils.isEmpty(authorization)) {
requestContext.setSendZuulResponse(false);//;拒绝访问
requestContext.setResponseStatusCode(200);//响应码
ResponseResult unauthenticated = new ResponseResult(CommonCode.UNAUTHENTICATED);
String jsonString = JSON.toJSONString(unauthenticated);
requestContext.setResponseBody(jsonString);
requestContext.getResponse().setContentType("application/json;charset=UTF‐8");
return null;
}
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer ")) {
log.error("http request header authorization is error");
return null;
}
try {
TokenInfo tokenInfo = getTokenInfo(authorization);
httpServletRequest.setAttribute("token", tokenInfo);
} catch (Exception e) {
log.error("token check error !", e);
}
return null;
}


}

对携带令牌的请求通过请求鉴权服务验证 token 合法后放行至下一过滤规则,反之返回 token 检查失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  private TokenInfo getTokenInfo(String authorization) {
String url = "http://localhost:50201/api/v1.0.1/auth-service/auth/oauth/check_token";
String token = StringUtils.substringAfter(authorization, "Bearer ");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// headers.setBasicAuth("RsWebApp", "RsWebApp");
//方法一 使用RestTemplateBuilder来实例化
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + token);
//此处相当于在header里头添加content-type等参数
headers.add(HttpHeaders.CONTENT_TYPE,"application/json");
RestTemplateBuilder builder = new RestTemplateBuilder();
RestTemplate restTemplate = builder.basicAuthorization("RsWebApp", "RsWebApp").build();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("token", token);
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, headers);
ResponseEntity<TokenInfo> exchange = restTemplate.exchange(url, HttpMethod.POST, httpEntity, TokenInfo.class);
return exchange.getBody();
}
  • 5.网关鉴权过滤规则

    拦截请求,校验 token 是否过期;对符合要求的请求在 header 中添加 jti (java web token identity)并予以放行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.czq.gateway.filter;

import com.czq.model.auth.ext.TokenInfo;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
* 授权过滤器
*/
@Slf4j
@Component
public class AuthorizationFilter extends ZuulFilter {
List<String> paths = new ArrayList<>();

private static final Logger LOG = LoggerFactory.getLogger(LoginFilter.class);

// @Autowired
// private RestTemplate restTemplate;

public AuthorizationFilter() {
super();
// paths.add("/login/logining");
// paths.add("/login/checkCode");
paths.add("/**/auth/oauth/check_token");
paths.add("/**/auth/oauth/token");
paths.add("/**/auth/userlogin");
paths.add("/**/auth/userjwt");
paths.add("/**/manage/captcha");
paths.add("/**/manage/verification");
paths.add("/ui/**");
paths.add("/**/swagger**/**");
paths.add("/**/v2/api-docs");
paths.add("/**/*.css");
paths.add("/**/*.jpg");
paths.add("/**/*.png");
paths.add("/**/*.gif");
paths.add("/**/*.js");
paths.add("/**/*.svg");

}

@Override
public boolean shouldFilter() {//执行过滤器
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String uri = request.getRequestURI();
PathMatcher matcher = new AntPathMatcher();
Optional<String> optional = paths.stream().filter(t -> matcher.match(t, uri)).findFirst();
return !optional.isPresent();
}

@Override
public Object run() throws ZuulException {

log.info("authorization start");

RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();

if (isNeedAuth(request)) {

// OAuthFilter token 校验通过,会将token信息放在请求头里
TokenInfo tokenInfo = (TokenInfo) request.getAttribute("token");

// 校验是否有权限
if (tokenInfo != null && tokenInfo.isActive()) {
if (!hasPermission(tokenInfo, request)) {
log.info("audit log update fail 403");
handleError(403, requestContext);
}

requestContext.addZuulRequestHeader("token", tokenInfo.getJti());
} else {
if (!StringUtils.startsWith(request.getRequestURI(), "/token")) {
log.info("audit log update fail 401");
handleError(401, requestContext);
}
}
}

return null;
}

private void handleError(int status, RequestContext requestContext) {
requestContext.getResponse().setContentType("application/json");
requestContext.setResponseStatusCode(status);
requestContext.setResponseBody("{\"message\":\"auth fail\"}");
requestContext.setSendZuulResponse(false);
}

private boolean hasPermission(TokenInfo tokenInfo, HttpServletRequest request) {
return true; //RandomUtils.nextInt() % 2 == 0;
}

private boolean isNeedAuth(HttpServletRequest request) {
return true;
}

@Override
public String filterType() {
return "pre";
}

@Override
public int filterOrder() {
return 3;
}

}

    1. token 信息实体类

TokenInfo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.czq.model.auth.ext;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.List;

@Data
@ToString
@NoArgsConstructor
public class TokenInfo {
String userpic;
String user_name;
List<String> scope;
String name;
String utype;
boolean active;
String id;
long exp;
String jti;
String client_id;
}

AuthToken.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.czq.model.auth.ext;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;


@Data
@ToString
@NoArgsConstructor
public class AuthToken {
String access_token;//访问token
String refresh_token;//刷新token
String jwt_token;//jwt令牌
}

Prev:
uvicorn 框架软件编写后发布至ubuntu服务器
Next:
Swagger + Zuul 整合微服务接口文档