微服务SpringCloud项目(六):整合oauth并使用

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

📖前言

1
复制代码心态好了,就没那么累了。心情好了,所见皆是明媚风景。

“一时解决不了的问题,那就利用这个契机,看清自己的局限性,对自己进行一场拨乱反正。”正如老话所说,一念放下,万般自在。如果你正被烦心事扰乱心神,不妨学会断舍离。断掉胡思乱想,社区垃圾情绪,离开负面能量。心态好了,就没那么累了。心情好了,所见皆是明媚风景。

🚓引入依赖


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
xml复制代码<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--freemarker,页面渲染引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- SpringBoot 监控客户端 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring-boot-admin.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 引入数据库密码加密 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>${jasypt.version}</version>
</dependency>

<!-- 引入mysql链接依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector}</version>
<scope>provided</scope>
</dependency>

<!-- 引入Druid依赖
java.sql.SQLFeatureNotSupportedException
http://www.vmfor.com/p/101494868463.html-->
<dependency>
<!--自动配置-->
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${common-pool.version}</version>
</dependency>

<!--<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>-->

<!-- 引入 MybatisPlus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>

<!-- 引入多数据源依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${mybatis-plus-dynamic.version}</version>
</dependency>

</dependencies>

1. 启动类添加


1
2
3
4
java复制代码@EnableFeignClients
//对外开启暴露获取token的API接口
@EnableResourceServer
@EnableDiscoveryClient

2. 创建一个授权的配置文件 AuthorizationServerConfig.java


根据官方指示我们需要创建一个配置类去实现 AuthorizationServerConfigurer 所以我们,创建一个类去继承它的实现类AuthorizationServerConfigurerAdapter,具体代码如下:

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
java复制代码package com.cyj.dream.auth.config;

import com.cyj.dream.auth.entity.SysUser;
import com.cyj.dream.auth.persistence.service.impl.CustomUserServiceImpl;
import com.cyj.dream.core.constant.Constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
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.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* @Description: 授权的配置文件
* @BelongsProject: DreamChardonnay
* @BelongsPackage: com.cyj.dream.auth.config
* @Author: ChenYongJia
* @CreateTime: 2021-09-30
* @Email: chen87647213@163.com
* @Version: 1.0
*/
// 授权认证服务中心配置
@Configuration // 配置类
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

@Autowired
private BCryptPasswordEncoder passwordEncoder;

@Autowired
private RedisConnectionFactory redisConnectionFactory;

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private DataSource dataSource;

@Autowired
private CustomUserServiceImpl userDetailsService;

/**
* accessToken 有效期 2小时
*/
private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 7200 * 12 * 7;

/**
* accessToken 有效期 2小时
*/
private static final int REFRESH_TOKEN_VALIDITY_SECONDS = 7200 * 12 * 7;

/**
* 定制化处理
* 配置tokenStore的存储方式是redis存储
* <p>
* 这里存储在数据库,大家可以结合自己的业务场景考虑将access_token存入数据库还是redis
*
* @return
*/
@Bean
public TokenStore redisTokenStore() {
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
// redis key 前缀--DreamCloud
tokenStore.setPrefix(Constant.tokenPrefix + "_");
return tokenStore;
}

/**
* 配置客户端的管理是jdbc
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("dream").secret(passwordEncoder.encode("c83fb51ff6e807e8805c6dd9d5707365"))
// 授权码授权模式下的回调地址
.redirectUris("http://www.baidu.com")
.authorizedGrantTypes("authorization_code", "password", "refresh_token").scopes("all")
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
// 授权类型
.refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);
//clients.withClientDetails(new JdbcClientDetailsService(dataSource));
}

/**
* 认证服务器Endpoints配置
*
* @return
* @author ChenYongJia
* @date 2021/9/30
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints.authenticationManager(authenticationManager).allowedTokenEndpointRequestMethods(HttpMethod.GET,
HttpMethod.POST, HttpMethod.PUT,
HttpMethod.DELETE)
.tokenEnhancer(tokenEnhancerChain)
//配置tokenStore管理、配置客户端详情--使用redis
.tokenStore(redisTokenStore()).userDetailsService(userDetailsService)
//配置授权模式
.tokenGranter(tokenGranter(endpoints));
//配置tokenServices的参数 +
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
//配置accessToken过期时间
defaultTokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(2));
//配置refreshToken的过期时间
defaultTokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
//设置支持刷新token
defaultTokenServices.setReuseRefreshToken(true);
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenStore(endpoints.getTokenStore());
defaultTokenServices.setClientDetailsService(endpoints.getClientDetailsService());
defaultTokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
endpoints.tokenServices(defaultTokenServices);
}

/**
* 认证服务器相关接口权限管理
*
* @return
* @author ChenYongJia
* @date 2021/9/30
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//配置允许表单访问
security.allowFormAuthenticationForClients()
.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}

/**
* 配置授权模式也可以添加自定义模式(不写也有默认的)
* 具体可查看AuthorizationServerEndpointsConfigurer中的getDefaultTokenGranters方法
* 以后添加一个手机验证码的功能
*
* @param endpoints
* @return
*/
private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> list = new ArrayList<>();
//增加刷新token
list.add(new RefreshTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
//授权码模式
list.add(new AuthorizationCodeTokenGranter(endpoints.getTokenServices(), endpoints.getAuthorizationCodeServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
//客户端凭证模式
list.add(new ClientCredentialsTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
//密码模式
list.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
//隐藏式
list.add(new ImplicitTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
return new CompositeTokenGranter(list);
}

/**
* 创建一个token增强方法,增加一些我们自己想要返回的附加信息
*
* @return
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
final Map<String, Object> additionalInfo = new HashMap<>(2);
additionalInfo.put("license", "SunnyChen-DreamChardonnay");
SysUser user = (SysUser) authentication.getUserAuthentication().getPrincipal();
if (user != null) {
additionalInfo.put("userId", user.getSysUserId());
additionalInfo.put("userPhone", user.getSysUserPhone());
additionalInfo.put("userDeptId", user.getSysUserInfoDepartmentId());
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
};
}

/**
* 用来做验证
*
* @return
*/
@Bean
AuthenticationManager authenticationManager() {
AuthenticationManager authenticationManager = new AuthenticationManager() {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return daoAuhthenticationProvider().authenticate(authentication);
}
};
return authenticationManager;
}

/**
* 用来做验证
*
* @return
*/
@Bean
public AuthenticationProvider daoAuhthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
return daoAuthenticationProvider;
}

}

3. 由于授权服务器本身也是资源服务器,所以也创建一个资源配置,代码如下


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
java复制代码package com.cyj.dream.auth.config;

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;

/**
* @Description: 资源配置器
* @BelongsProject: DreamChardonnay
* @BelongsPackage: com.cyj.dream.auth.config
* @Author: ChenYongJia
* @CreateTime: 2021-09-30
* @Email: chen87647213@163.com
* @Version: 1.0
*/
@Configuration
// 启用资源服务
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

/**
* 配置资源接口安全,http.authorizeRequests()针对的所有url,但是由于登录页面url包含在其中,这里配置会进行token校验,校验不通过返回错误json,
* 而授权码模式获取code时需要重定向登录页面,重定向过程并不能携带token,所有不能用http.authorizeRequests(),
* 而是用requestMatchers().antMatchers(""),这里配置的是需要资源接口拦截的url数组
*
* @param http
* @return void
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http //配置需要保护的资源接口
.requestMatchers().
antMatchers("/user", "/test/need_token", "/logout", "/remove", "/update", "/test/need_admin", "/test/scope")
.and().authorizeRequests().anyRequest().authenticated();
}

}

4. 创建 webSecurity 配置


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tex复制代码/*
* OK ,关于这个配置我要多说两句:
*
* 1.首先当我们要自定义Spring Security的时候我们需要继承自WebSecurityConfigurerAdapter来完成,相关配置重写对应
* 方法即可。 2.我们在这里注册CustomUserService的Bean,然后通过重写configure方法添加我们自定义的认证方式。
* 3.在configure(HttpSecurity http)方法中,我们设置了登录页面,而且登录页面任何人都可以访问,然后设置了登录失败地址,也设置了注销请求,
* 注销请求也是任何人都可以访问的。
* 4.permitAll表示该请求任何人都可以访问,.anyRequest().authenticated(),表示其他的请求都必须要有权限认证。
* 5.这里我们可以通过匹配器来匹配路径,比如antMatchers方法,假设我要管理员才可以访问admin文件夹下的内容,我可以这样来写:.
* antMatchers("/admin/**").hasRole("ROLE_ADMIN"),也可以设置admin文件夹下的文件可以有多个角色来访问,
* 写法如下:.antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")
* 6.可以通过hasIpAddress来指定某一个ip可以访问该资源,假设只允许访问ip为210.210.210.210的请求获取admin下的资源,
* 写法如下.antMatchers("/admin/**").hasIpAddress("210.210.210.210")
* 7.更多的权限控制方式参看源码:
*/

代码如下:

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
java复制代码package com.cyj.dream.auth.config;

import com.cyj.dream.auth.handler.*;
import com.cyj.dream.auth.persistence.service.impl.CustomUserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
* @Description: WebSecurity配置
* @BelongsProject: DreamChardonnay
* @BelongsPackage: com.cyj.dream.auth.config
* @Author: ChenYongJia
* @CreateTime: 2021-09-30
* @Email: chen87647213@163.com
* @Version: 1.0
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
private CustomUserServiceImpl userDetailsService;

/**
* 自定义登录成功处理器
*/
@Autowired
private MyAuthenticationSuccessHandler userLoginSuccessHandler;

/**
* 自定义登录失败处理器
*/
@Autowired
private MyAuthenticationFailureHandler userLoginFailureHandler;

/**
* 自定义注销成功处理器
*/
@Autowired
private UserLogoutSuccessHandler userLogoutSuccessHandler;

/**
* 自定义暂无权限处理器
*/
@Autowired
private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;

/**
* 自定义未登录的处理器
*/
@Autowired
private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;

@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserServiceImpl();
}

/**
* 配置登录验证逻辑
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}

/**
* 认证管理--需要配置这个支持password模式
* <p>
* support password grant type
*
* @return 认证管理对象
* @throws Exception 认证异常信息
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

/**
* 安全请求配置,这里配置的是Security的部分,请求全部通过,安全拦截在资源服务器配置
* <p>
* http安全配置
*
* @param http http安全对象
* @throws Exception http安全异常信息
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置允许的请求以及跨域问题
http.authorizeRequests()
.antMatchers("/webjars/**", "/js/**", "/css/**", "/images/*", "/fonts/**", "/**/*.png", "/**/*.jpg",
"/static/**")
// .anyRequest()
.permitAll()
// 所有请求都可以访问,本地开发打开下面一行的注释
//.antMatchers("/**").permitAll()
//不进行权限验证的请求或资源(从配置文件中读取),本地开发可以,将本行注释掉
.antMatchers("/login", "/oauth/**")
.permitAll()
.antMatchers("/**")
.fullyAuthenticated()
.and()
//配置未登录自定义处理类
.httpBasic()
.authenticationEntryPoint(userAuthenticationEntryPointHandler)
.and()
.csrf()
.disable()
// 配置登录地址
.formLogin()
// 本地
//.loginPage("/login/userLogin")
//.loginProcessingUrl("/login/userLogin")
.loginPage("/login")
//配置登录成功自定义处理类
.successHandler(userLoginSuccessHandler)
//配置登录失败自定义处理类
.failureHandler(userLoginFailureHandler)
.permitAll()
.and()
//配置登出地址
.logout()
// /userInfo/loginOutByToken
.logoutUrl("/login/loginOut")
//配置用户登出自定义处理类
.logoutSuccessHandler(userLogoutSuccessHandler)
.and()
//配置没有权限自定义处理类
.exceptionHandling()
.accessDeniedHandler(userAuthAccessDeniedHandler)
.and()
// 开启跨域
.cors()
.and()
// 取消跨站请求伪造防护
.csrf().disable();
/*.cors().and()
.addFilterAt(ignoreLogoutFilter, LogoutFilter.class);*/
// 为了可以使用 iframe 内嵌页面加载
http.headers().frameOptions().sameOrigin();
// 基于Token不需要session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 禁用缓存
http.headers().cacheControl();
}

@Override
public void configure(WebSecurity web) throws Exception {
// 设置忽略资源
web.ignoring().antMatchers(
"/error",
"/v2/api-docs/**",
"/favicon.ico",
"/css/**",
"/js/**",
"/images/*",
"/fonts/**",
"/**/*.png",
"/**/*.jpg")
// 不拦截 swagger2 所进行的配置
.antMatchers("/templates/**")
.antMatchers("/static/**")
.antMatchers("/webjars/**")
.antMatchers("/swagger-ui.html/**")
.antMatchers("/v2/**")
.antMatchers("/doc.html")
.antMatchers("/api-docs-ext/**")
.antMatchers("/swagger-resources/**");
;
}

}

5. 创建一个 controller 进行访问测试


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
java复制代码package com.cyj.dream.auth.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cyj.dream.auth.entity.SysUser;
import com.cyj.dream.auth.persistence.service.ITbSysUserService;
import com.cyj.dream.core.aspect.annotation.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

/**
* @Description: 用户控制器
* @BelongsProject: DreamChardonnay
* @BelongsPackage: com.cyj.dream.auth.controller
* @Author: ChenYongJia
* @CreateTime: 2021-09-30 10:40
* @Email: chen87647213@163.com
* @Version: 1.0
*/
@Slf4j
@ResponseResult
@RestController
@RequestMapping(value = "/authUser", name = "用户控制器")
@Api(value = "authUser", tags = "用户控制器")
public class UserController {

@Autowired
private ITbSysUserService iTbSysUserService;

@ApiOperation("通过名称获取用户信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "userName", value = "用户名称", dataType = "String", required = true)
})
@RequestMapping(value = "getByName", method = RequestMethod.GET, name = "通过名称获取用户信息")
public SysUser getByName(@RequestParam(value = "userName") String userName){
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUsername, userName);
return iTbSysUserService.getOne(wrapper);
}

@ApiOperation("获取授权的用户信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "principal", value = "当前用户", dataType = "Principal", required = true)
})
@RequestMapping(value = "current", method = RequestMethod.GET, name = "获取授权的用户信息")
public Principal user(Principal principal){
// 授权信息
return principal;
}

}

token 的请求

image-20200910202739906

不带 token 的请求

image-20200910202804042

token,但是没有ROLE_ADMIN权限

image-20200910203005094

6. 最后关于Fegin 调用服务 Token 丢失的问题


在微服务中我们经常会使用RestTemplate或Fegin来进行服务之间的调用,在这里就会出现一个问题,我们去调用别的服务的时候就会出现token丢失的情况,导致我们没有权限去访问。

所以我们需要加上一些拦截器将我们的 token 带走,针对fegin的配置代码如下:

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
java复制代码public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
// 设置请求头
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
requestTemplate.header(name, value);
}
}

// 设置请求体,这里主要是为了传递 access_token
Enumeration<String> parameterNames = request.getParameterNames();
StringBuilder body = new StringBuilder();
if (parameterNames != null) {
while (parameterNames.hasMoreElements()) {
String name = parameterNames.nextElement();
String value = request.getParameter(name);

// 将 Token 加入请求头
if ("access_token".equals(name)) {
requestTemplate.header("authorization", "Bearer " + value);
}

// 其它参数加入请求体
else {
body.append(name).append("=").append(value).append("&");
}
}
}

// 设置请求体
if (body.length() > 0) {
// 去掉最后一位 & 符号
body.deleteCharAt(body.length() - 1);
requestTemplate.body(body.toString());
}
}
}

然后将这个拦截器加入到我们 fegin 请求拦截器中:

1
2
3
4
5
6
7
java复制代码@Configuration
public class FeignRequestConfiguration {
@Bean
public RequestInterceptor requestInterceptor() {
return new FeignRequestInterceptor();
}
}

7. Spring SecurityShiro


相同点:

1:认证功能

2:授权功能

3:加密功能

4:会话管理

5:缓存支持

6:rememberMe功能…….

不同点:

优点:

1:Spring Security基于Spring开发,项目中如果使用Spring作为基础,配合Spring Security做权限更加方便,而Shiro需要和Spring进行整合开发

2:Spring Security功能比Shiro更加丰富些,例如安全防护

3:Spring Security社区资源比Shiro丰富

缺点:

1:Shiro的配置和使用比较简单,Spring Security上手复杂

2:Shiro依赖性低,不需要任何框架和容器,可以独立运行,而Spring Security依赖于Spring容器

8. 数据库表

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
sql复制代码-- used in tests that use HSQL
create table oauth_client_details (
client_id VARCHAR(256) PRIMARY KEY,
resource_ids VARCHAR(256),
client_secret VARCHAR(256),
scope VARCHAR(256),
authorized_grant_types VARCHAR(256),
web_server_redirect_uri VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove VARCHAR(256)
);

create table oauth_client_token (
token_id VARCHAR(256),
token LONGVARBINARY,
authentication_id VARCHAR(256) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256)
);

create table oauth_access_token (
token_id VARCHAR(256),
token LONGVARBINARY,
authentication_id VARCHAR(256) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256),
authentication LONGVARBINARY,
refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
token_id VARCHAR(256),
token LONGVARBINARY,
authentication LONGVARBINARY
);

create table oauth_code (
code VARCHAR(256), authentication LONGVARBINARY
);

create table oauth_approvals (
userId VARCHAR(256),
clientId VARCHAR(256),
scope VARCHAR(256),
status VARCHAR(10),
expiresAt TIMESTAMP,
lastModifiedAt TIMESTAMP
);

-- customized oauth_client_details table
create table ClientDetails (
appId VARCHAR(256) PRIMARY KEY,
resourceIds VARCHAR(256),
appSecret VARCHAR(256),
scope VARCHAR(256),
grantTypes VARCHAR(256),
redirectUrl VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additionalInformation VARCHAR(4096),
autoApproveScopes VARCHAR(256)
);

最后感谢大家耐心观看完毕,具体请求和一些结果返回可以参见上一章内容,留个点赞收藏是您对我最大的鼓励!


🎉总结:

  • 更多参考精彩博文请看这里:《陈永佳的博客》
  • 整合 auth 这里有一些类我没有放置,大家可以先阅读文章,后续我会把代码传到 git 上方便大家参考学习~
  • 喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%