学习必须往深处挖,挖的越深,基础越扎实!
阶段1、深入多线程
阶段2、深入多线程设计模式
阶段3、深入juc源码解析
码哥源码部分
码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】
码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】
码哥源码-原理源码篇【doug lea为什么要将成员变量赋值给局部变量后再操作?】
码哥讲源码【谁再说spring不支持多线程事务,你给我抽他!】
打脸系列【020-3小时讲解mesi协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】
导读:为了保证我们微服务的安全性,本章主要内容是使用oauth2.0给我们微服务加上安全校验。
概念
为了保证服务的安全性,往往都会在接口调用时做权限校验。在分布式架构中我们会把复杂的业务拆成多个微服务,这样不得不在所有服务中都实现这样的权限校验逻辑,这样就会有很多代码和功能冗余。所以在微服务架构中一般会独立出一个单独的认证授权服务,供其他所有服务调用。
在springcloud体系中,我们只对网关层开放外网访问权限,其他后端微服务做网络隔离,所有外部请求必须要通过网关才能访问到后端服务。在网关层对请求进行转发时先校验用户权限,判断用户是否有权限访问。
我们一般使用oauth2.0来实现对所有后端服务的统一认证授权。这期内容不讲oauth2.0协议,只讲实现过程。如果大家对oauth2.0不是很了解,可以翻看我之前的博客。
oauth2认证服务
建立认证服务auth-service
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
<groupid>com.alibaba.cloud</groupid>
<artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-oauth2</artifactid>
</dependency>
<!--database-->
<dependency>
<groupid>mysql</groupid>
<artifactid>mysql-connector-java</artifactid>
</dependency>
<dependency>
<groupid>com.baomidou</groupid>
<artifactid>mybatis-plus-boot-starter</artifactid>
</dependency>
<dependency>
<groupid>com.jianzh5.cloud</groupid>
<artifactid>cloud-common</artifactid>
</dependency>
</dependencies>
引入mysql主要是我们需要将oauth2.0的客户端信息以及token认证存入数据库。
建立相关数据表
主要是导入oauth2相关数据表以及用户表,在实际开发过程中用户权限这一套应该是基于rbac进行设计,这里为了方便演示我就直接只做一个用户表。
(oauth2.0相关表结构大家可以在网上找,如果找不到可以联系我)
给用户表添加数据:
insert into `user` values ('1', '$2a$10$gexkdt3nkofkfw1cflqquufji3azhg.w4pe3/wxhkang3tpksjrfw', 'zhangjian', 'admin');
注意:在spring-security 5.x版本必须要注入密码实现器,我们使用了 bcryptpasswordencoder
加密器,所以需要这里也需要对密码进行加密
添加client信息,使用 oauth_client_details
表:
insert into `oauth_client_details` values ('app', 'app', '$2a$10$fg7ou8cnxdesvflim7lrnedmipwbrxgm2w6.cogpddfqpyzxiqxe6', 'web', 'implicit,client_credentials,authorization_code,refresh_token,password', 'http://www.baidu.com', 'role_user', null, null, null, null);
同理也需要对client_secret字段进行加密
application.yml配置文件
spring:
main:
allow-bean-definition-overriding: true
application:
name: auth-service
cloud:
nacos:
discovery:
server-addr: xx.xx.xx.xx:8848/
datasource:
type: com.zaxxer.hikari.hikaridatasource
url: jdbc:mysql://xx.xx.xx.xx:3306/oauth2_config?characterencoding=utf8&zerodatetimebehavior=converttonull&usessl=false
username: root
password: xxxxxx
driver-class-name: com.mysql.jdbc.driver
server:
port: 5000
mybatis-plus:
mapper-locations: classpath:/mapper/*mapper.xml
自定义认证服务器
只需要继承 authorizationserverconfigureradapter
并在开始处加上@enableauthorizationserver
注解即可
/**
* <p>
* <code>authorizationserverconfig</code>
* </p>
* description:
* 授权/认证服务器配置
* @author javadaily
* @date 2024/2/26 16:26
*/
@configuration
@enableauthorizationserver
public class authorizationserverconfig extends authorizationserverconfigureradapter {
@autowired
private userdetailserviceimpl userdetailservice;
// 认证管理器
@autowired
private authenticationmanager authenticationmanager;
@autowired
private datasource datasource;
/**
* access_token存储器
* 这里存储在数据库,大家可以结合自己的业务场景考虑将access_token存入数据库还是redis
*/
@bean
public tokenstore tokenstore() {
return new jdbctokenstore(datasource);
}
/**
* 从数据库读取clientdetails相关配置
* 有inmemoryclientdetailsservice 和 jdbcclientdetailsservice 两种方式选择
*/
@bean
public clientdetailsservice clientdetails() {
return new jdbcclientdetailsservice(datasource);
}
/**
* 注入密码加密实现器
*/
@bean
public passwordencoder passwordencoder(){
return new bcryptpasswordencoder();
}
/**
* 认证服务器endpoints配置
*/
@override
public void configure(authorizationserverendpointsconfigurer endpoints) throws exception {
//如果需要使用refresh_token模式则需要注入userdetailservice
endpoints.userdetailsservice(userdetailservice);
endpoints.authenticationmanager(this.authenticationmanager);
endpoints.tokenstore(tokenstore());
}
/**
* 认证服务器相关接口权限管理
*/
@override
public void configure(authorizationserversecurityconfigurer security) throws exception {
security.allowformauthenticationforclients() //如果使用表单认证则需要加上
.tokenkeyaccess("permitall()")
.checktokenaccess("isauthenticated()");
}
/**
* client存储方式,此处使用jdbc存储
*/
@override
public void configure(clientdetailsserviceconfigurer clients) throws exception {
clients.withclientdetails(clientdetails());
}
}
自定义web安全配置类
/**
* <p>
* <code>websecurityconfig</code>
* </p>
* description:
* 自定义web安全配置类
* @author javadaily
* @date 2024/2/26 16:35
*/
@configuration
@enablewebsecurity
@enableglobalmethodsecurity(prepostenabled = true)
public class websecurityconfig extends websecurityconfigureradapter {
@override
@bean
public userdetailsservice userdetailsservice(){
return new userdetailserviceimpl();
}
@bean
public passwordencoder passwordencoder(){
return new bcryptpasswordencoder();
}
/**
* 认证管理
* @return 认证管理对象
* @throws exception 认证异常信息
*/
@override
@bean
public authenticationmanager authenticationmanagerbean() throws exception {
return super.authenticationmanagerbean();
}
@override
protected void configure(authenticationmanagerbuilder auth) throws exception {
auth.userdetailsservice(userdetailsservice())
.passwordencoder(passwordencoder());
}
/**
* http安全配置
* @param http http安全对象
* @throws exception http安全异常信息
*/
@override
protected void configure(httpsecurity http) throws exception {
http.authorizerequests()
.anyrequest().authenticated()
.and().httpbasic()
.and().cors()
.and().csrf().disable();
}
@override
public void configure(websecurity web) throws exception {
web.ignoring().antmatchers(
"/error",
"/static/**",
"/v2/api-docs/**",
"/swagger-resources/**",
"/webjars/**",
"/favicon.ico"
);
}
}
自定义用户实现
@service
public class userdetailserviceimpl implements userdetailsservice {
@autowired
private usermapper usermapper;
@override
public userdetails loaduserbyusername(string username) throws usernamenotfoundexception {
//获取本地用户
user user = usermapper.selectbyusername(username);
if(user != null){
//返回oauth2的用户
return new org.springframework.security.core.userdetails.user(
user.getusername(),
user.getpassword(),
authorityutils.createauthoritylist(user.getrole())) ;
}else{
throw new usernamenotfoundexception("用户["+username+"]不存在");
}
}
}
实现 userdetailsservice
接口并实现 loaduserbyusername
方法,这一部分大家根据自己的技术框架实现,dao层我就不贴出来了。
对外提供获取当前用户接口
@restcontroller
@requestmapping("user")
public class usercontroller {
@autowired
public usermapper usermapper;
@getmapping("getbyname")
public user getbyname(){
return usermapper.selectbyusername("zhangjian");
}
/**
* 获取授权的用户信息
* @param principal 当前用户
* @return 授权信息
*/
@getmapping("current/get")
public principal user(principal principal){
return principal;
}
}
资源服务器
oauth2.0的认证服务器也资源服务器,我们在启动类上加入 @enableresourceserver
注解即可
@springbootapplication
//对外开启暴露获取token的api接口
@enableresourceserver
@enablediscoveryclient
public class authserverapplication {
public static void main(string[] args) {
springapplication.run(authserverapplication.class, args);
}
}
后端微服务改造
后端所有微服务都是资源服务器,所以我们需要对其进行改造,下面以account-service为例说明改造过程
- 添加oauth2.0依赖
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-oauth2</artifactid>
</dependency>
- 配置资源服务器
@configuration
@enableresourceserver
public class resourceserverconfig extends resourceserverconfigureradapter {
@override
public void configure(httpsecurity http) throws exception {
http
.authorizerequests()
.requestmatchers(endpointrequest.toanyendpoint()).permitall()
.antmatchers(
"/v2/api-docs/**",
"/swagger-resources/**",
"/swagger-ui.html",
"/webjars/**"
).permitall()
.anyrequest().authenticated()
.and()
//统一自定义异常
.exceptionhandling()
.and()
.csrf().disable();
}
}
- 修改配置文件,配置身份获取地址
security:
oauth2:
resource:
user-info-uri: http://localhost:5000/user/current/get
id: account-service
测试
-
我们直接访问account-service接口,会提示需要需要认证授权
-
通过密码模式从认证服务器获取access_token
-
在请求头上带上access_token重新访问account-service接口,接口正常响应
-
通过debug模式发现每次访问后端服务时都会去认证资源器获取当前用户
-
输入错误的access_token进行访问,提示access_token失效
总结
通过以上几步我们将后端服务加上了认证服务,必须要先进行认证才能正常访问后端服务。整个实现过程还是比较复杂的,建议大家都实践一下,理解其中相关配置的作用,也方便更深入理解oauth2协议。
发表评论