目录
在springsecurity+oauth2.0 搭建认证中心和资源服务中心-csdn博客
基础上整合网关和jwt实现分布式统一认证授权。
大致流程如下:
1、客户端发出请求给网关获取令牌
2、网关收到请求,直接转发给授权服务
3、授权服务验证用户名、密码等一系列身份,通过则颁发令牌给客户端
4、客户端携带令牌请求资源,请求直接到了网关层
5、网关对令牌进行校验(验签、过期时间校验....)、鉴权(对当前令牌携带的权限)和访问资源所需的权限进行比对,如果权限有交集则通过校验,直接转发给微服务
6、微服务进行逻辑处理
1. oauth2.0授权服务
导入依赖
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-security</artifactid>
<version>2.2.5.release</version>
</dependency>
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-oauth2</artifactid>
<version>2.2.5.release</version>
</dependency>
<dependency>
<groupid>mysql</groupid>
<artifactid>mysql-connector-java</artifactid>
<version>8.0.28</version>
</dependency>
<dependency>
<groupid>com.baomidou</groupid>
<artifactid>mybatis-plus-boot-starter</artifactid>
<version>3.3.2</version>
</dependency>
<dependency>
<groupid>org.springframework.security</groupid>
<artifactid>spring-security-oauth2-resource-server</artifactid>
</dependency>
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<version>1.18.30</version>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-test</artifactid>
</dependency>
<dependency>
<groupid>com.alibaba.cloud</groupid>
<artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid>
</dependency>
</dependencies>
application.yaml
server:
port: 8080
spring:
application:
name: oauth2-cloud-auth-server
cloud:
nacos:
## 注册中心配置
discovery:
# nacos的服务地址,nacos-server中ip地址:端口号
server-addr: 127.0.0.1:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.driver
url: jdbc:mysql://localhost:3306/rbac?useunicode=true&characterencoding=utf-8&servertimezone=utc
username: root
password: 123456
这里展示部分代码
accesstokenconfig类
/**
* 令牌的配置
*/
@configuration
public class accesstokenconfig {
/**
* jwt的秘钥
* todo 实际项目中需要统一配置到配置文件中,资源服务也需要用到
*/
private final static string sign_key="jwt";
/**
* 令牌的存储策略
*/
@bean
public tokenstore tokenstore() {
//使用jwttokenstore生成jwt令牌
return new jwttokenstore(jwtaccesstokenconverter());
}
/**
* jwtaccesstokenconverter
* tokenenhancer的子类,在jwt编码的令牌值和oauth身份验证信息之间进行转换。
* todo:后期可以使用非对称加密
*/
@bean
public jwtaccesstokenconverter jwtaccesstokenconverter(){
jwtaccesstokenconverter converter = new jwtaccesstokenconverter();
// 设置秘钥
converter.setsigningkey(sign_key);
return converter;
}
@bean
passwordencoder passwordencoder() {
return new bcryptpasswordencoder();
}
}
myauthorizationconfig类
@configuration
@enableauthorizationserver
public class myauthorizationconfig extends authorizationserverconfigureradapter {
@autowired
private tokenstore tokenstore;
/**
* 客户端存储策略,这里使用内存方式,后续可以存储在数据库
*/
@autowired
private clientdetailsservice clientdetailsservice;
/**
* security的认证管理器,密码模式需要用到
*/
@autowired
private authenticationmanager authenticationmanager;
@autowired
private jwtaccesstokenconverter jwtaccesstokenconverter;
/**
* 配置令牌访问的安全约束
*/
@override
public void configure(authorizationserversecurityconfigurer security) throws exception {
security
//开启/oauth/token_key验证端口权限访问
.tokenkeyaccess("permitall()")
//开启/oauth/check_token验证端口认证权限访问
.checktokenaccess("permitall()")
//表示支持 client_id 和 client_secret 做登录认证
.allowformauthenticationforclients();
}
//配置客户端
@override
public void configure(clientdetailsserviceconfigurer clients) throws exception {
//内存模式
clients.inmemory()
//客户端id
.withclient("test")
//客户端秘钥
.secret(new bcryptpasswordencoder().encode("123456"))
//资源id,唯一,比如订单服务作为一个资源,可以设置多个
.resourceids("order")
//授权模式,总共四种,1. authorization_code(授权码模式)、password(密码模式)、client_credentials(客户端模式)、implicit(简化模式)
//refresh_token并不是授权模式,
.authorizedgranttypes("authorization_code","password","client_credentials","implicit","refresh_token")
//允许的授权范围,客户端的权限,这里的all只是一种标识,可以自定义,为了后续的资源服务进行权限控制
.scopes("all")
//false 则跳转到授权页面
.autoapprove(false)
//授权码模式的回调地址
.redirecturis("http://www.baidu.com"); //可以and继续添加客户端
}
@bean
public authorizationservertokenservices tokenservices() {
defaulttokenservices services = new defaulttokenservices();
//客户端端配置策略
services.setclientdetailsservice(clientdetailsservice);
//支持令牌的刷新
services.setsupportrefreshtoken(true);
//令牌服务
services.settokenstore(tokenstore);
//access_token的过期时间
services.setaccesstokenvalidityseconds(60 * 60 * 2);
//refresh_token的过期时间
services.setrefreshtokenvalidityseconds(60 * 60 * 24 * 3);
//设置令牌增强,使用jwt
services.settokenenhancer(jwtaccesstokenconverter);
return services;
}
/**
* 授权码模式的service,使用授权码模式authorization_code必须注入
*/
@bean
public authorizationcodeservices authorizationcodeservices() {
//授权码存在内存中
return new inmemoryauthorizationcodeservices();
}
/**
* 配置令牌访问的端点
*/
@override
public void configure(authorizationserverendpointsconfigurer endpoints) throws exception {
endpoints
//授权码模式所需要的authorizationcodeservices
.authorizationcodeservices(authorizationcodeservices())
//密码模式所需要的authenticationmanager
.authenticationmanager(authenticationmanager)
//令牌管理服务,无论哪种模式都需要
.tokenservices(tokenservices())
//只允许post提交访问令牌,uri:/oauth/token
.allowedtokenendpointrequestmethods(httpmethod.post);
}
}
securityconfig类
/**
* spring security的安全配置
*/
@configuration
@enablewebsecurity
public class securityconfig extends websecurityconfigureradapter {
/**
* 加密算法
*/
@autowired
jwttokenuserdetailsservice userdetailsservice;
@override
protected void configure(httpsecurity http) throws exception {
//todo 允许表单登录
http.authorizerequests()
.anyrequest().authenticated()
.and()
.formlogin()
.loginprocessingurl("/login")
.permitall()
.and()
.csrf()
.disable();
}
@override
protected void configure(authenticationmanagerbuilder auth) throws exception {
//从数据库中查询用户信息
auth.userdetailsservice(userdetailsservice);
}
/**
* authenticationmanager对象在oauth2认证服务中要使用,提前放入ioc容器中
* oauth的密码模式需要
*/
@override
@bean
public authenticationmanager authenticationmanagerbean() throws exception {
return super.authenticationmanagerbean();
}
}
2. 资源服务
导入依赖
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
<groupid>cn.hutool</groupid>
<artifactid>hutool-all</artifactid>
</dependency>
<dependency>
<groupid>com.alibaba</groupid>
<artifactid>fastjson</artifactid>
<version>1.2.78</version>
</dependency>
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<version>1.18.30</version>
</dependency>
<dependency>
<groupid>com.alibaba.cloud</groupid>
<artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid>
</dependency>
application.yaml
server:
port: 8081
spring:
application:
name: oauth2-cloud-service
cloud:
nacos:
## 注册中心配置
discovery:
# nacos的服务地址,nacos-server中ip地址:端口号
server-addr: 127.0.0.1:8848
authenticationfilter类
@component
public class authenticationfilter extends onceperrequestfilter {
/**
* 具体方法主要分为两步
* 1. 解密网关传递的信息
* 2. 将解密之后的信息封装放入到request中
*/
@override
protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain filterchain) throws servletexception, ioexception {
//获取请求头中的用户信息
string token = request.getheader("token");
if (token!=null){
//解密
string json = base64.decodestr(token);
jsonobject jsonobject = json.parseobject(json);
//获取用户身份信息、权限信息
string principal = jsonobject.getstring("user_name");
jsonarray tempjsonarray = jsonobject.getjsonarray("authorities");
//权限
string[] authorities = tempjsonarray.toarray(new string[0]);
//放入loginval
loginval loginval = new loginval();
loginval.setusername(principal);
loginval.setauthorities(authorities);
//放入request的attribute中
request.setattribute("login_message",loginval);
}
filterchain.dofilter(request,response);
}
}
servicecontroller类
@restcontroller
public class servicecontroller {
@requestmapping("/test")
public loginval test(httpservletrequest httpservletrequest){
return (loginval)httpservletrequest.getattribute("login_message");
}
}
3. gateway网关
导入依赖
<dependencies>
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-gateway</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-security</artifactid>
<version>2.2.5.release</version>
</dependency>
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-oauth2</artifactid>
<version>2.2.5.release</version>
</dependency>
<dependency>
<groupid>org.springframework.security</groupid>
<artifactid>spring-security-oauth2-resource-server</artifactid>
</dependency>
<dependency>
<groupid>com.alibaba.cloud</groupid>
<artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid>
</dependency>
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
</dependency>
<dependency>
<groupid>cn.hutool</groupid>
<artifactid>hutool-all</artifactid>
</dependency>
<dependency>
<groupid>com.alibaba</groupid>
<artifactid>fastjson</artifactid>
<version>1.2.78</version>
</dependency>
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-loadbalancer</artifactid>
</dependency>
</dependencies>
application.yaml
server:
port: 7000
spring:
main:
allow-bean-definition-overriding: true
application:
name: oauth2-cloud-gateway
cloud:
nacos:
## 注册中心配置
discovery:
# nacos的服务地址,nacos-server中ip地址:端口号
server-addr: 127.0.0.1:8848
gateway:
## 路由
routes:
## id只要唯一即可,名称任意
- id: oauth2-cloud-auth-server
uri: lb://oauth2-cloud-auth-server
predicates:
## path route predicate factory断言
- path=/oauth/**
- id: oauth2-cloud-order
uri: lb://oauth2-cloud-service
predicates:
## path route predicate factory断言
- path=/test/**
oauth2:
cloud:
sys:
parameter:
ignoreurls:
- /oauth/token
- /oauth/authorize
accesstokenconfig类
/**
* 令牌的配置
*/
@configuration
public class accesstokenconfig {
private final static string sign_key="jwt";
/**
* 令牌的存储策略
*/
@bean
public tokenstore tokenstore() {
//使用jwttokenstore生成jwt令牌
return new jwttokenstore(jwtaccesstokenconverter());
}
/**
* jwtaccesstokenconverter
* tokenenhancer的子类,在jwt编码的令牌值和oauth身份验证信息之间进行转换。
* todo:后期可以使用非对称加密
*/
@bean
public jwtaccesstokenconverter jwtaccesstokenconverter(){
jwtaccesstokenconverter converter = new jwtaccesstokenconverter();
// 设置秘钥
converter.setsigningkey(sign_key);
return converter;
}
}
jwtaccessmanager类
@slf4j
@component
//经过认证管理器jwtauthenticationmanager认证成功后,就需要对令牌进行鉴权,如果该令牌无访问资源的权限,则不允通过。
public class jwtaccessmanager implements reactiveauthorizationmanager<authorizationcontext> {
@override
public mono<authorizationdecision> check(mono<authentication> mono, authorizationcontext authorizationcontext) {
uri uri = authorizationcontext.getexchange().getrequest().geturi();
//设计权限角色,这里简单写一下,实际上应该从数据库或者缓存中获取
list<string> authorities = new arraylist<>();
authorities.add("role_admin");
//认证通过且角色匹配的用户可访问当前路径
return mono
//判断是否认证成功
.filter(authentication::isauthenticated)
//获取认证后的全部权限
.flatmapiterable(authentication::getauthorities)
.map(grantedauthority::getauthority)
//如果权限包含则判断为true
.any(authorities::contains)
.map(authorizationdecision::new)
.defaultifempty(new authorizationdecision(false));
}
}
jwtauthenticationmanager类
/**
* jwt认证管理器,主要的作用就是对携带过来的token进行校验,比如过期时间,加密方式等
* 一旦token校验通过,则交给鉴权管理器进行鉴权
*/
@component
public class jwtauthenticationmanager implements reactiveauthenticationmanager {
/**
* 使用jwt令牌进行解析令牌
*/
@autowired
private tokenstore tokenstore;
@override
public mono<authentication> authenticate(authentication authentication) {
return mono.justorempty(authentication)
.filter(a -> a instanceof bearertokenauthenticationtoken)
.cast(bearertokenauthenticationtoken.class)
.map(bearertokenauthenticationtoken::gettoken)
.flatmap((accesstoken -> {
oauth2accesstoken oauth2accesstoken = this.tokenstore.readaccesstoken(accesstoken);
//根据access_token从数据库获取不到oauth2accesstoken
if (oauth2accesstoken == null) {
return mono.error(new invalidtokenexception("无效的token!"));
} else if (oauth2accesstoken.isexpired()) {
return mono.error(new invalidtokenexception("token已过期!"));
}
oauth2authentication oauth2authentication = this.tokenstore.readauthentication(accesstoken);
if (oauth2authentication == null) {
return mono.error(new invalidtokenexception("无效的token!"));
} else {
return mono.just(oauth2authentication);
}
})).cast(authentication.class);
}
}
securityconfig类
@configuration
@enablewebfluxsecurity
public class securityconfig {
/**
* jwt的鉴权管理器
*/
@autowired
private reactiveauthorizationmanager<authorizationcontext> accessmanager;
@autowired
private requestauthenticationentrypoint requestauthenticationentrypoint;
@autowired
private requestaccessdeniedhandler requestaccessdeniedhandler;
/**
* 系统参数配置
*/
@autowired
private sysparameterconfig sysconfig;
/**
* token校验管理器
*/
@autowired
private reactiveauthenticationmanager tokenauthenticationmanager;
@autowired
private corsfilter corsfilter;
@bean
securitywebfilterchain webfluxsecurityfilterchain(serverhttpsecurity http) throws exception{
//认证过滤器,放入认证管理器tokenauthenticationmanager
authenticationwebfilter authenticationwebfilter = new authenticationwebfilter(tokenauthenticationmanager);
authenticationwebfilter.setserverauthenticationconverter(new serverbearertokenauthenticationconverter());
http
.httpbasic().disable()
.csrf().disable()
.authorizeexchange()
//白名单直接放行
.pathmatchers(arrayutil.toarray(sysconfig.getignoreurls(),string.class)).permitall()
//其他的请求必须鉴权,使用鉴权管理器
.anyexchange().access(accessmanager)
//异常处理
.and().exceptionhandling()
.authenticationentrypoint(requestauthenticationentrypoint)
.accessdeniedhandler(requestaccessdeniedhandler)
.and()
// 跨域过滤器
.addfilterat(corsfilter, securitywebfiltersorder.cors)
//token的认证过滤器,用于校验token和认证
.addfilterat(authenticationwebfilter, securitywebfiltersorder.authentication);
return http.build();
}
}
requestaccessdeniedhandler
/**
* 自定义返回结果:没有权限访问时
*/
@component
public class requestaccessdeniedhandler implements serveraccessdeniedhandler {
@override
public mono<void> handle(serverwebexchange exchange, accessdeniedexception denied) {
serverhttpresponse response = exchange.getresponse();
response.setstatuscode(httpstatus.ok);
response.getheaders().add(httpheaders.content_type, mediatype.application_json_value);
string body= jsonutil.tojsonstr(new resultmsg(1005,"无权限访问",null));
databuffer buffer = response.bufferfactory().wrap(body.getbytes(charset.forname("utf-8")));
return response.writewith(mono.just(buffer));
}
}
globalauthenticationfilter
/**
* 全局过滤器,对token的拦截,解析token放入header中,便于下游微服务获取用户信息
* 分为如下几步:
* 1、白名单直接放行
* 2、校验token
* 3、读取token中存放的用户信息
* 4、重新封装用户信息,加密成功json数据放入请求头中传递给下游微服务
*/
@component
@slf4j
public class globalauthenticationfilter implements globalfilter, ordered {
/**
* jwt令牌的服务
*/
@autowired
private tokenstore tokenstore;
/**
* 系统参数配置
*/
@autowired
private sysparameterconfig sysconfig;
@override
public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
string requesturl = exchange.getrequest().getpath().value();
//1、白名单放行,比如授权服务、静态资源.....
if (checkurls(sysconfig.getignoreurls(),requesturl)){
return chain.filter(exchange);
}
//2、 检查token是否存在
string token = gettoken(exchange);
if (stringutils.isblank(token)) {
return invalidtokenmono(exchange);
}
//3 判断是否是有效的token
oauth2accesstoken oauth2accesstoken;
try {
//解析token,使用tokenstore
oauth2accesstoken = tokenstore.readaccesstoken(token);
map<string, object> additionalinformation = oauth2accesstoken.getadditionalinformation();
system.out.println(additionalinformation);
//取出用户身份信息
string user_name = additionalinformation.get("user_name").tostring();
//获取用户权限
list<string> authorities = (list<string>) additionalinformation.get("authorities");
//将用户名和权限进行base64加密
jsonobject jsonobject=new jsonobject();
jsonobject.put("user_name", user_name);
jsonobject.put("authorities",authorities);
string base = base64.encode(jsonobject.tojsonstring());
// serverhttprequest 中的 mutate 方法是用于创建一个修改后的请求对象的方法,而不改变原始请求对象。这个方法是为了在处理请求过程中创建一个新的请求对象,以便进行一些修改或增强。
serverhttprequest tokenrequest = exchange.getrequest().mutate().header("token",base).build();
serverwebexchange build = exchange.mutate().request(tokenrequest).build();
return chain.filter(build);
} catch (invalidtokenexception e) {
//解析token异常,直接返回token无效
return invalidtokenmono(exchange);
}
}
@override
public int getorder() {
return 0;
}
/**
* 对url进行校验匹配
*/
private boolean checkurls(list<string> urls,string path){
antpathmatcher pathmatcher = new antpathmatcher();
for (string url : urls) {
if (pathmatcher.match(url,path))
return true;
}
return false;
}
/**
* 从请求头中获取token
*/
private string gettoken(serverwebexchange exchange) {
string tokenstr = exchange.getrequest().getheaders().getfirst("authorization");
if (stringutils.isblank(tokenstr)) {
return null;
}
string token = tokenstr.split(" ")[1];
if (stringutils.isblank(token)) {
return null;
}
return token;
}
/**
* 无效的token
*/
private mono<void> invalidtokenmono(serverwebexchange exchange) {
return buildreturnmono(resultmsg.builder()
.code(1004)
.msg("无效的token")
.build(), exchange);
}
private mono<void> buildreturnmono(resultmsg resultmsg, serverwebexchange exchange) {
serverhttpresponse response = exchange.getresponse();
byte[] bits = json.tojsonstring(resultmsg).getbytes(standardcharsets.utf_8);
databuffer buffer = response.bufferfactory().wrap(bits);
response.setstatuscode(httpstatus.unauthorized);
response.getheaders().add("content-type", "application/json;charset:utf-8");
return response.writewith(mono.just(buffer));
}
}
sysparameterconfig
@configurationproperties(prefix = "oauth2.cloud.sys.parameter")
@data
@component
public class sysparameterconfig {
/**
* 白名单
*/
private list<string> ignoreurls;
}
4. 测试
代码链接:gateway+springsecurity+oauth2.0+jwt实现分布式统一认证授权资源-csdn文库
发表评论