1.简单介绍
本文是基于SpringBoot2 + SpringSecurityOAuth2.0版本实现的
在线流程图如下:
2.环境的搭建
创建一个父工程,主要做版本控制
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<lombok.version>1.18.26</lombok.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
<mybatis-plus.version>3.5.3.2</mybatis-plus.version>
<mysql.version>8.0.32</mysql.version>
<druid.version>1.2.18</druid.version>
<fastjson.version>1.2.80</fastjson.version>
<commons-pool2.version>2.11.1</commons-pool2.version>
<okhttps.version>3.1.1</okhttps.version>
<okio.version>2.8.0</okio.version>
<spring-cloud-starter-oauth2.version>2.2.5.RELEASE</spring-cloud-starter-oauth2.version>
<jjwt.version>0.10.5</jjwt.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--spring-cloud依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud ablibaba依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Sa-Token Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${commons-pool2.version}</version>
</dependency>
<!--mybatis plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mybatis plus常用注解依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mysql8驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--druid数据源连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- ok https -->
<dependency>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
<version>${okhttps.version}</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>${okio.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>${spring-cloud-starter-oauth2.version}</version>
</dependency>
<!--添加jwt相关的包-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
3.授权服务搭建
3.1 创建数据表
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`
(
`client_id` VARCHAR(256) CHARACTER SET utf8 NOT NULL COMMENT '客户端唯一标识ID',
`resource_ids` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端所能访问的资源id集合',
`client_secret` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端访问密匙',
`scope` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端申请的权限范围',
`authorized_grant_types` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端授权类型',
`web_server_redirect_uri` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端的重定向URI',
`authorities` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '客户端所拥有的权限值',
`access_token_validity` INT(11) DEFAULT NULL COMMENT '客户端access_token的有效时间(单位:秒)',
`refresh_token_validity` INT(11) DEFAULT NULL,
`additional_information` VARCHAR(4096) CHARACTER SET utf8 DEFAULT NULL COMMENT '预留的字段',
`autoapprove` VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL COMMENT '是否跳过授权(true是,false否)',
PRIMARY KEY (`client_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='客户端授权表';
-- ----------------------------
-- Records of oauth_client_details 加密前的密码为 123123
-- ----------------------------
INSERT INTO `oauth_client_details`
VALUES ('1001', NULL, '$2a$10$CE1GKj9eBZsNNMCZV2hpo.QBOz93ojy9mTd9YQaOy8H4JAyYKVlm6', 'all',
'authorization_code,password,refresh_token', 'https://www.baidu.com', NULL, 3600, 864000, NULL, NULL);
INSERT INTO `oauth_client_details`
VALUES ('1002', '', '$2a$10$4gbIfJBDuLtzB8EnLnP24eKQIMfXKPD6qJ8Lzklx5h9XeEt.VM/0C', 'read,write',
'password,refresh_token', NULL, NULL, 3600, 864000, NULL, NULL);
INSERT INTO `oauth_client_details`
VALUES ('1003', NULL, '$2a$10$APF9tE9z9Z74rcFZlUjvTeGpmH2XP1BdVTVrT6CLzTtSUVDNt2uJW', 'read,write',
'password,refresh_token', NULL, NULL, 3600, 864000, NULL, NULL);
这里的密文是通过 Spring Security 提供的加密类得到的
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
System.out.println(passwordEncoder.encode("123123"));
3.2 引入依赖
<dependencies>
<!-- 公共模块 -->
<dependency>
<groupId>com.zhouzz</groupId>
<artifactId>my-security-cloud-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring security oauth2-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--nacos注册中心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--nacos配置中心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--springcloud负载均衡依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- openfeign 服务远程调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mybatis plus常用注解依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
</dependency>
<!--mysql8驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!--druid数据源连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
3.3 yml配置
server:
port: 9000
spring:
application:
name: auth-server
cloud:
nacos:
server-addr: 192.168.254.150:8848 #注册中心地址
username: nacos
password: nacos
discovery:
namespace: b943ac45-87c5-41e2-b83f-5863c3ac4581
group: DEFAULT_GROUP
service: ${spring.application.name}
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.254.150:3306/sc_auth?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8
username: root
password: 123456
druid:
initial-size: 5 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 20 #最大连接数
web-stat-filter:
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" #不统计这些请求数据
stat-view-servlet: #访问监控网页的登录用户名和密码
login-username: druid
login-password: druid
hs:
jwt:
keyPairName: jwt.jks
keyPairAlias: jwt
keyPairSecret: 123456
keyPairStoreSecret: 123456
3.4 配置SpringSecurity
WebSecurityConfig.java
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private MyUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 实现UserDetailsService获取用户信息
auth.userDetailsService(userDetailsService);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// oauth2 密码模式需要拿到这个bean
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().permitAll()
.and().authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.anyRequest()
.authenticated()
.and().logout().permitAll()
.and().csrf().disable();
}
}
这里需要我们创建一个UserDetailsService接口类型的bean,能够根据username获取到用户信息
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserFeign userFeign;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 通过OpenFeign 远程调用user微服务获取用户相关的信息
Result<User> commonResult = userFeign.queryUser(username);
User user = commonResult.getData();
if (null == user) {
log.warn("用户{}不存在", username);
throw new UsernameNotFoundException(username);
}
log.info("pwd1:" + user.getPassword());
// 对user进行一个封装
return new UserDetailsWrapper(user);
}
}
public class UserDetailsWrapper implements UserDetails {
private User user;
public UserDetailsWrapper() {
}
public UserDetailsWrapper(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//返回当前用户的权限
return Arrays.asList(new SimpleGrantedAuthority(user.getRole()));
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return user.getUserStatus() == 1;
}
}
自定义一个配置类,添加@EnableAuthorizationServer
注解,并继承AuthorizationServerConfigurerAdapter
类,使用ctrl+O
快捷键重写父类中的方法
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
/**
* 我们自定义的查询用户信息的service类
*/
@Autowired
private MyUserDetailsService userDetailsService;
/**
* 在SpringSecurity配置文件中,往Spring容器中添加了一个AuthenticationManager类型的bean
*/
@Autowired
private AuthenticationManager authenticationManagerBean;
/**
* 指定token存储策略是jwt
*/
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/**
* 对jwt生成的token增强,添加等多的用户信息至token中
*/
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
/**
* 认证服务器的安全配置
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 第三方客户端校验token需要带入 clientId 和 client secret 来校验
security.checkTokenAccess("isAuthenticated()")
// 来获取我们的tokenKey需要带入clientId,client secret
.tokenKeyAccess("isAuthenticated()");
//允许表单认证
security.allowFormAuthenticationForClients();
}
/**
* 配置客户端属性
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 配置授权服务器存储第三方客户端的信息 基于DB存储 oauth_client_details
clients.withClientDetails(clientDetails());
}
@Bean
public ClientDetailsService clientDetails() {
// JdbcClientDetailsService 就会去操作oauth_client_details数据表
return new JdbcClientDetailsService(dataSource);
}
/**
* 配置授权服务器端点的非安全特性:如token store、token
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置JWT的内容增强器
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManagerBean)
// refresh_token是否重复使用
.reuseRefreshTokens(false)
// 刷新令牌授权包含对用户信息的检查
.userDetailsService(userDetailsService)
// 支持GET,POST请求
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
//指定token存储策略是jwt
.tokenStore(tokenStore)
//使用jwt令牌格式
.accessTokenConverter(jwtAccessTokenConverter)
// jwt token增强,添加更多的 用户信息至token中
.tokenEnhancer(enhancerChain);
}
}
4.授权码模式
启动 网关,认证,用户服务。
4.1 前端获取code
调用localhost:9000/oauth/authorize
接口,携带请求类型为code授权码、client_id
为 1001
,scope
范围为all
。
访问url:http://localhost:9000/oauth/authorize?response_type=code&client_id=1001&redirect_uri=https://www.zmall.com&scope=all
这里没有登录则会跳转到登录页, 需要进行登录,用户名:zhangsan,密码:123456
之后,我们直接选择 Approve
,oauth服务会302重定向请求跳转到 https://www.zmall.com/?code=GeKh5p
。
通过nginx 在 www.zmall.com 域名指定的 index.html 页面中会有下面一段 script 脚本通过刷新该页面或者重定向到此页面时执行。
<script type="text/javascript">
// 根据code授权码进行登录
function doLogin(code) {
$.ajax({
url: '/codeLogin?code=' + code,
dataType: 'json',
success: function(res) {
console.log('返回:', res);
if(res.code == 200) {
setInfo(res.data);
layer.msg('登录成功!');
} else {
layer.msg(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
//刷新该页面或者重定向该页面时触发
var code = getParam('code');
if(code) {
doLogin(code);
}
</script>
上面就可以判断 https://www.zmall.com/?code=GeKh5p
地址中包含code参数,之后前端会通过 ajax发送请求到自己的服务端的 codeLogin
接口。
4.2 服务端发起请求获取token
服务端获取前端的接口请求后, 组装一些必要参数:
访问url: http://auth-server/oauth/token?grant_type=authorization_code&client_id=1001&client_secret=123123&scope=all&code=GeKh5p&redirect_uri=https://www.baidu.com
服务端配置好地址后,就通过服务注册发现通过网关路由到认证服务。之后就能得到如下token信息。
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJhZGRpdGlvbmFsSW5mbyI6eyJ1c2VyTmFtZSI6InpoYW5nc2FuIiwidXNlcklkIjoxfSwiZXhwIjoxNzI2OTQ5NDc4LCJhdXRob3JpdGllcyI6WyJ1c2VyIl0sImp0aSI6IjViZTM5NGZlLWM3ZDctNDgyMy1iY2FhLTlkN2MxN2M4Zjk0ZCIsImNsaWVudF9pZCI6IjEwMDEifQ.npJBHXNYqyARUQhQpom_yg6vY-D0YhoegA5wly2yZEmjZIetBRxavDj1ZlX0D_yOj6mJbk5azJQMAes5fUmA4tkI_uOPJje0yHRFuoYQYQdeW-TjUGQgUGBcI8bFFJkIsxWqDYis8TqKg3NEfzMnEc93A1ud_dWMFmStDAK2TXvipNIR3jZQjisyIsbtbMglmvgh4r9Z-4_eW6qv0QaEOJ56-Y0t5V8bTzzx-zJpFwFLPvEVX4bEo0uvDJYZWztOqs5cHOf7V7gkV8egQzAOaF55MWF1SiUSW8Lc9Iz1ne-IOnrq-uYPvVqJ9B2UyhsPMbHnVG771-x7ufSPqD6Dsw",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI1YmUzOTRmZS1jN2Q3LTQ4MjMtYmNhYS05ZDdjMTdjOGY5NGQiLCJhZGRpdGlvbmFsSW5mbyI6eyJ1c2VyTmFtZSI6InpoYW5nc2FuIiwidXNlcklkIjoxfSwiZXhwIjoxNzI3ODA2Mjc4LCJhdXRob3JpdGllcyI6WyJ1c2VyIl0sImp0aSI6IjljMmJlODM5LWNiMzQtNDQ3Ny04YjViLTM2N2JmNTRlYWYxNyIsImNsaWVudF9pZCI6IjEwMDEifQ.F8suGaqYbhyvxzBb0iCQr_GRvF_jqvhTjBXF7SVzdKwUzsCWpRhRw9PqpvjK_BIF56efzWk13KcwNwAWBKz2kTM9eDVE1VBJSWJcW8sfClKQvVWaTpouTX6lh5d_92t6qcAU_fVdhk3P5Qx4jSU5qUUVmTfk16RoFqpJX-wRAyFvPRgagStMMar3ZK_q3775MD02aO9cfKMfL_99fYRZ_GZkDXYrc7CHehE4y9tDH7YGzNypQi3h_0hnUusdbf5SjJ6PPKPKHFdzdsPYLdmQgTyoNYCpMqjZf3Sv0bRbNPYKfjttJ6hYjryVA3UCqvOr7y2keahg2o6fZHMqGDGc7A",
"expires_in": 7199,
"scope": "all",
"additionalInfo": {
"userName": "zhangsan",
"userId": 1
},
"jti": "5be394fe-c7d7-4823-bcaa-9d7c17c8f94d"
}
5.密码模式
如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。
如下,直接使用用户名和密码进行获取token
测试获取token,grant_type为password,并携带用户的用户名和密码、client_id+client_secret+scope这些都是要和客户端注册时的信息对应上
http://localhost:9000/oauth/token?username=zhangsan&password=123456&grant_type=password&client_id=1001&client_secret=123123&scope=all
6.校验token接口
因为授权服务器的security配置需要携带clientId和clientSecret,可以采用basic Auth的方式发请求
http://localhost:9000/oauth/check_token?token=TOKEN值
7.刷新token
刷新token,根据 refresh_token值,填写必要参数:
http://localhost:9000/oauth/token?refresh_token=REFRESH_TOKEN值&grant_type=refresh_token&client_id=1001&client_secret=123123&scope=all
8.使用非对称加密与JWT
若使用对称加密的流程是:gateway网关需要对每一次请求,都要调用授权服务器进行token校验
如果使用非对称加密,那么gateway网关启动时从授权服务器拿一次公钥,以后的请求就直接在网关中进行token验证,直接使用公钥对token进行校验,就省了请求授权服务器进行token校验的请求了
第一步:生成jks 证书文件
我们使用jdk自动的工具生成,指定密钥生成的位置需要提前创建目录
命令格式
keytool
-genkeypair 生成密钥对
-alias jwt(别名)
-keypass 123456(别名密码)
-keyalg RSA(生证书的算法名称,RSA是一种非对称加密算法)
-keysize 1024(密钥长度,证书大小)
-validity 365(证书有效期,天单位)
-keystore D:/jwt/jwt.jks(指定生成证书的位置和证书名称)
-storepass 123456(获取keystore信息的密码)
-storetype (指定密钥仓库类型)
使用 "keytool -help" 获取所有可用命令
keytool -genkeypair -alias jwt -keyalg RSA -keysize 2048 -keystore D:/demo/jwt/jwt.jks
执行结果:
这里会在 D:\demo\jwt
目录生成 jwt.jks
文件。
查看公钥信息
keytool -list -rfc --keystore jwt.jks | openssl x509 -inform pem -pubkey
因为windows不能使用openssl命令,我就直接使用的git命令窗执行的,但是这里有中文显示问题,不过结果还是正常输出了
$ keytool -list -rfc --keystore jwt.jks | openssl x509 -inform pem -pubkey
▒▒▒▒▒▒Կ▒▒▒▒▒: 123456
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqHX4VU7abfo9Xl4dR0hh
pbJCdQjnN/9IIt7YtI+ImpA7/IlOL1Irf1PYwc48JzWPNkEjlm2SHWdi1Ol5Xr4A
jqUOq3h5MdEmG3wm3/deNGihqqWqrQjUuEdvzQMeHefWpZiVUzees/lyiDMzYO74
jmfC+sPCaKn9Cck+o1lQvKaOk+m/tFeyTrCAo5M2K7Moi6gSgch17nUqVhifAUtV
WJv9luBRYI2CENVaYiRhzg0u5D2I6aBHXBJ4AJAA0eb1v6ZQ5i9LUdBOBbHc7ttG
Lto1lo217JpvKiRoR58WoSLCC6TrCPe+EVVoiZK4yEXgbduhQtNCGjwmctuv3I9O
4QIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIIDVzCCAj+gAwIBAgIETUQyxDANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJj
bjEOMAwGA1UECBMFaHVuYW4xETAPBgNVBAcTCGNoYW5nc2hhMQswCQYDVQQKEwJ6
bDEOMAwGA1UECxMFem1hbGwxDTALBgNVBAMTBHpob3UwHhcNMjQwOTIxMTg0MjE4
WhcNMjQxMjIwMTg0MjE4WjBcMQswCQYDVQQGEwJjbjEOMAwGA1UECBMFaHVuYW4x
ETAPBgNVBAcTCGNoYW5nc2hhMQswCQYDVQQKEwJ6bDEOMAwGA1UECxMFem1hbGwx
DTALBgNVBAMTBHpob3UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo
dfhVTtpt+j1eXh1HSGGlskJ1COc3/0gi3ti0j4iakDv8iU4vUit/U9jBzjwnNY82
QSOWbZIdZ2LU6XlevgCOpQ6reHkx0SYbfCbf9140aKGqpaqtCNS4R2/NAx4d59al
mJVTN56z+XKIMzNg7viOZ8L6w8Joqf0JyT6jWVC8po6T6b+0V7JOsICjkzYrsyiL
qBKByHXudSpWGJ8BS1VYm/2W4FFgjYIQ1VpiJGHODS7kPYjpoEdcEngAkADR5vW/
plDmL0tR0E4Fsdzu20Yu2jWWjbXsmm8qJGhHnxahIsILpOsI974RVWiJkrjIReBt
26FC00IaPCZy26/cj07hAgMBAAGjITAfMB0GA1UdDgQWBBQ2mVEEOyrf67cq9TrT
Jgrj0AJ9jzANBgkqhkiG9w0BAQsFAAOCAQEANY1SHdg/9tCblrnZEKPl5ZS7CcwJ
bHZxmECoO2FAlKl3jzntQMb66Zt/oHH2ON9q90iPIn8e9CjWuyOkauLnyK/H9eQj
zS4gXxV8tQmo4BagrCqDoS1OYlkp7Nb4guFA8h/rnIwCo1wWBnfzzNPB3trUGZP2
msOm99t/2UuBOoW8n41DuOPVFX/RR8e5btc0gUG2UL+zo5sbSL7HbFyoV265q/sQ
buWUFI130o+QQ9K1bRE7gkBfeJXLsaJMH73IP+trI+725jlVXBRdQ0TTXC00UktZ
F//+KmRcsyvscHnjwrE6uqclIlnlfvsmKi17IGs/RX+X8fXFNLRTvAIFRQ==
-----END CERTIFICATE-----
将生成的jwt.jks文件拷贝到授权服务器的resource目录下。
第二步:授权服务中增加jwt的属性配置类
在yml配置文件中添加配置
hs:
jwt:
keyPairName: jwt.jks
keyPairAlias: jwt
keyPairSecret: 123456
keyPairStoreSecret: 123456
创建一个读取上面配置的类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "hs.jwt")
public class JwtCAProperties {
/**
* 证书名称
*/
private String keyPairName;
/**
* 证书别名
*/
private String keyPairAlias;
/**
* 证书私钥
*/
private String keyPairSecret;
/**
* 证书存储密钥
*/
private String keyPairStoreSecret;
}
之后把配置类加入Spring Security中。
9.自定义模式
不用Oauth2的四种模式,直接用自己的方式实现一个手机验证码登录功能。
大概得流程就是:
9.1 自定义认证令牌
extends AbstractAuthenticationToken
自由实现未认证和已认证的构造令牌的方法
9.2 自定义授权模式
extends AbstractTokenGranter
在授权模式中我们会获取到认证路径中的手机号和验证码,并产生一个未认证的认证令牌交给Oauth2
9.3 自定义实际认证提供者
implements AuthenticationProvider
在实际的认证者中我们会拿到在授权模式中提供的手机号和验证码,然后进行业务比对,比如是否存在、是否过期等等。如果全部验证通过会产生一个完成认证的认证令牌交给Oauth2
9.4 授权配置
extends AuthorizationServerConfigurerAdapter
这个里面需要配置 客户端
ClientDetailsServiceConfigurer
以及将自定义和默认授权模式交给Oauth2 AuthorizationServerEndpointsConfigurer
9.5 Oauth2配置
extends WebSecurityConfigurerAdapter
配置哪些请求需要进行拦截和免登录
configure(HttpSecurity http)
配置密码模式的用户名和密码,并将自定义认证提供者加入
configure(AuthenticationManagerBuilder auth)
9.6 资源服务器配置
extends ResourceServerConfigurerAdapter
定义哪些资源需要被拦截和放行
configure(HttpSecurity http)
如果是生成环境需要配置token策略和服务端保持一致。
10.小结
用例代码:my-security
评论区