SpringCloud微服务安全网关安全 3-5 重构代码以使用真实环境
Posted 鮀城小帅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud微服务安全网关安全 3-5 重构代码以使用真实环境相关的知识,希望对你有一定的参考价值。
1. 通过OAuth2 Toke的Scope参数控制权限
1.1 在服务端认证服务器里,通过配置客户端的Scope,可以控制给这个客户端生成的token有哪些权限
1.2 在客户端,申请令牌的时候,可以指定scope
示例:在资源服务器 (this-order-api)里,控制post请求的token ,其scope必须包含write权限,get请求的token必须包含read权限。
用postman客户端(clientId=orderApp)去认证服务器申请一个scpoe=read的token,去调用资源服务器里的Post请求:
2. 将token转换为用户信息
目前在资源服务器里,想要获取用户信息,在Controller里,可以通过 @AuthenticationPrincipal 注解,获取生成token的用户名。但是获取不到用户的其他信息,如userId等。
主要方式为:
- 在资源服务器的安全配置: OAuth2WebSecurityConfig 里,的 tokenServices方法里,配置一个 AccessTokenConverter,用来将token信息转换为 User 信息
- 新建UserDetailsService的实现类
2.1 添加注解后的接口
@PostMapping
public OrderInfo create(@RequestBody OrderInfo info,
@AuthenticationPrincipal User user){
log.info("user is {}" ,user.getId());
OrderInfo orderInfo = new OrderInfo();
return orderInfo;
}
2.2 OAuth2WebSecurityConfig.java
/**
* @ClassName OAuth2WebSecurityConfig
* @Description TODO token验证
* @Author wushaopei
* @Date 2021/5/3 13:38
* @Version 1.0
*/
/**
* 怎么验发往本服务的请求头的令牌
* 1,自定义tokenServices ,说明去哪里去验token
* 2,重写authenticationManagerBean()方法,将AuthenticationManager暴露为一个Bean
* 要认证跟用户相关的信息,一般用 AuthenticationManager
*
* 这样配置了后,所有发往this-order-api的请求,
* 需要验token的时候就会发请求去http://localhost:9090/oauth/check_token验token,获取到token对应的用户信息
*/
@Configuration
@EnableWebSecurity
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
/**
* 要认证跟用户相关的信息,一般用 AuthenticationManager
* 覆盖这个方法,可以将AuthenticationManager暴露为一个Bean
* @return
* @throws Exception
*/
@Bean
public ResourceServerTokenServices tokenServices(){
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId("orderService");
tokenServices.setClientSecret("123456");
tokenServices.setCheckTokenEndpointUrl("http://localhost:9090/oauth/check_token");
tokenServices.setAccessTokenConverter(getAccessTokenConverter());
return tokenServices;
}
//转换器,将token转换为用户信息
private AccessTokenConverter getAccessTokenConverter() {
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
//这个类的目的是设UserDetailsService,来将token转换为用户信息,不设默认为空
DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
userTokenConverter.setUserDetailsService(userDetailsService);
accessTokenConverter.setUserTokenConverter(userTokenConverter);
return accessTokenConverter;
}
/**
* 要认证跟用户相关的信息,一般用 AuthenticationManager
* 覆盖这个方法,可以将AuthenticationManager暴露为一个Bean
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
OAuth2AuthenticationManager auth2AuthenticationManager = new OAuth2AuthenticationManager();
auth2AuthenticationManager.setTokenServices(tokenServices());
return auth2AuthenticationManager;
}
}
UserDetailsServiceImpl.java
/**
* @ClassName UserDetailsServiceImpl
* @Description TODO
* @Author wushaopei
* @Date 2021/5/3 15:26
* @Version 1.0
*/
@Component("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//这里就不去读数据库了
User user = new User();
user.setId(1L);
user.setUsername(username);
return user;
}
}
User对象,实现UserDetails接口:
/**
* @ClassName User
* @Description TODO
* @Author wushaopei
* @Date 2021/5/3 15:22
* @Version 1.0
*/
@Data
public class User implements UserDetails {
private Long id;
private String username;
private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN");
}
@Override
public boolean isAccountNonExpired() {
return true; //账号没过期
}
@Override
public boolean isAccountNonLocked() {
return true;//账号没被锁定
}
@Override
public boolean isCredentialsNonExpired() {
return true;//密码没过期
}
@Override
public boolean isEnabled() {
return true;//是否可用
}
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
然后在订单Controller里,就可以取到用户的 id等其他属性了:
用 @AuthenticationPrincipal User user 注解可以取出User对象。
用 @AuthenticationPrincipal(expression = "#this.id") Long id 可以取出User里面的属性
2.3 测试结果:
3. token 持久化到数据库
3.1 说明
客户端应用 持久化到数据库
之前的章节里,客户端信息是在配置在代码里的,是存在内存里的,这样新增或删除一个客户端应用,都要改代码,然后还要重启认证服务器。
token 持久化到数据库
之前的章节里,token信息都是存在内存里的,这样的话重启服务器后,token就没了。而且如果认证服务器是集群的话,发令牌的是A机器,验令牌的可能是B机器,这样也是不行的,需要将token持久化到数据库或者redis。
Spring默认提供了OAuth2相关的表,建表语句如下( mysql ):
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 BLOB,
authentication_id VARCHAR(256) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256)
);
create table oauth_access_token (
token_id VARCHAR(256),
token BLOB,
authentication_id VARCHAR(256) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256),
authentication BLOB,
refresh_token VARCHAR(256)
);
create table oauth_refresh_token (
token_id VARCHAR(256),
token BLOB,
authentication BLOB
);
create table oauth_code (
code VARCHAR(256), authentication BLOB
);
create table oauth_approvals (
userId VARCHAR(256),
clientId VARCHAR(256),
scope VARCHAR(256),
status VARCHAR(10),
expiresAt DATETIME,
lastModifiedAt DATETIME
);
将以上脚本执行到MYSQL。
3.2 代码重构
(1)由于要连数据,所以保证认证服务器配置了jdbc相关配置,我之前已配置过,用的是mybatis-plus,这里就不再赘述。
(2)将客户端信息保存到表里
之前是在代码里配置:
(3)将客户端配置在数据库里:
(4)配置TokenStore,将token信息持久化
配置TokenStore
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore(){
return new JdbcTokenStore(dataSource);
}
告诉服务器,存取token的时候,去自定义的tokenStore里去存取token
/**
* @Description TODO 配置用戶信息
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 传给他一个authenticationManager用来校验传过来的用户信息是不是合法的,注进来一个,自己实现
// endpoints.authenticationManager(authenticationManager); 原来的代码
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
(5)完整代码
package com.imooc.security.server.auth;
import jdk.nashorn.internal.ir.annotations.Reference;
import org.checkerframework.checker.units.qual.A;
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.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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
/**
* @ClassName OAuth2AuthServerConfig
* @Description TODO 认证服务器
* @Author wushaopei
* @Date 2021/5/2 21:44
* @Version 1.0
*/
@Configuration //这是一个配置类
@EnableAuthorizationServer // 当前应用是一个认证服务器
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
//Spring 对密码加密的封装,自己配置下
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore(){
return new JdbcTokenStore(dataSource);
}
public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("123456"));
}
/***
* @Description 配置客户端应用的信息,让认证服务器知道有哪些客户端应用来申请令牌。
* @param clients 客户端的详情服务的配置
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
// clients.inMemory() //添加客户端应用,配置在内存里,后面修改为数据库里
// .withClient("orderApp")// 指定client的id,应用的用户名,这里添加的是客户端应用
// .secret(passwordEncoder.encode("123456")) // 应用的密码
// .scopes("read", "write") // 应用的权限
// .accessTokenValiditySeconds(3600) // 令牌的有效期,单位为秒s
// .resourceIds("order-server") // 资源服务器的id。指:我发给orderApp的token可以访问哪些资源服务器
// .authorizedGrantTypes("password") // 授权方式,指可以用哪种方式去实现
// .and()
// .withClient("orderService")// 指定client的id,应用的用户名,这里添加的是订单服务。微服务中,订单服务应该具备访问其他服务的权利,同样需要获取令牌
// .secret(passwordEncoder.encode("123456")) // 应用的密码
// .scopes("read") // 应用的权限
// .accessTokenValiditySeconds(3600) // 令牌的有效期,单位为秒s
// .resourceIds("order-server") // 资源服务器的id。指:我发给orderApp的token可以访问哪些资源服务器
// .authorizedGrantTypes("password") // 授权方式,指可以用哪种方式去实现
// .and()
// .withClient("gateway")// 指定client的id,应用的用户名,这里添加的是订单服务。微服务中,订单服务应该具备访问其他服务的权利,同样需要获取令牌
// .secret(passwordEncoder.encode("123456")) // 应用的密码
// .scopes("read", "write") // 应用的权限
// .accessTokenValiditySeconds(3600) // 令牌的有效期,单位为秒s
// .resourceIds("order-server") // 资源服务器的id。指:我发给orderApp的token可以访问哪些资源服务器
// .authorizedGrantTypes("password"); // 授权方式,指可以用哪种方式去实现
}
/**
* @Description TODO 配置用戶信息
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 传给他一个authenticationManager用来校验传过来的用户信息是不是合法的,注进来一个,自己实现
// endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
/**
* @Description TODO 配置资源服务器过来验token的规则
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
/**
* 过来验令牌有效性的请求,不是谁都能验的,必须要是经过身份认证的。
* 所谓身份认证就是,必须携带clientId,clientSecret,否则随便一请求过来验token是不验的
*/
security.checkTokenAccess("isAuthenticated()");
}
}
重新请求令牌:
以上是关于SpringCloud微服务安全网关安全 3-5 重构代码以使用真实环境的主要内容,如果未能解决你的问题,请参考以下文章
SpringCloud微服务安全网关安全 3-2 常见的微服务安全整体架构
重学SpringCloud系列八之微服务网关安全认证-JWT篇
SpringCloud微服务安全网关安全 3-3 搭建OAuth2 认证服务器
SpringCloud微服务安全网关安全 3-8 用开源项目spring-cloud-zuul-ratelimit 做网关上的限流