授权之基于JWT完成单点登录
- 摘要
- 单点登录简介
- 创建oauth2-client
- 网页单点服务验证
- 服务调用验证
摘要
Spring Cloud Security 为构建安全的SpringBoot应用提供了一系列解决方案,结合Oauth2可以实现单点登录功能,本文将对其单点登录用法进行详细介绍。
单点登录简介
单点登录简称SSO,只的是当有多个系统需要登录时,用户只需要登录一个系统就无须登录其他的系统
创建oauth2-client
这里新建一个oauth2-client作为客服端,使用oauth2-jwt-server服务作授权服务,当我们在oauth2-jwt-server登录了,就可以直接调用oauth2-client的接口,反之,则跳转oauth2-jwt-server进行登录
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-security</artifactId>
</dependency>
</dependencies>
server:
port: 9300
servlet:
session:
cookie:
# 防止cookie冲突,冲突会导致登录验证不通过
name: OAUTH2-CLIENT-SESSIONID
spring:
application:
name: oauth-client
oauth2-service-url: http://localhost:9200
security:
oauth2:
client:
# 申请的client 的id(对应jwt服务的client-id)
client-id: admin
# 申请的client 的秘钥(对应jwt服务的client-secret)
client-secret: admin
# 获取code的地址
user-authorization-uri: ${oauth2-service-url}/oauth/authorize
# 拿到code获取access-token的URL
access-token-uri: ${oauth2-service-url}/oauth/token
resource:
jwt:
key-uri: ${oauth2-service-url}/oauth/token_key
启动类需要加上@EnableOAuth2Sso表示开启SSO验证
@EnableOAuth2Sso
@SpringBootApplication
public class Oauth2ClientApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2ClientApplication.class, args);
}
}
添加用于验证的客户端接口
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/getUser")
Object getUser(Authentication authentication) {
return authentication;
}
}
修改jwt服务的授权服务器(AuthorizationServerConfig)
package com.zglx.jwt.config;
import com.zglx.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.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.JwtAccessTokenConverter;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: 授权服务器
* @Author: Zlx
* @Date: 2021/10/17 5:09 下午
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
@Qualifier("redisTokenStore")
private TokenStore tokenStore;
@Qualifier("jwtTokenStore")
@Autowired
private TokenStore jwtTokenStore;
@Autowired
@Qualifier("jwtAccessTokenConverter")
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
/**
* 使用密码模式需要配置
*
* @param endpoints endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// // 自定义JWT内容增强器
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
// 配置jwt内容增强器
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManager)
// 配置用户数据源,登录的时候会执行userService的loadUserByUsername方法
// 登录地址
// http://localhost:9401/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all&state=normal
.userDetailsService(userService)
// 设置存储方式,这里使用redis
// .tokenStore(tokenStore);
.tokenStore(jwtTokenStore)
.tokenEnhancer(tokenEnhancerChain)
// 设置自定义token构造器
.accessTokenConverter(jwtAccessTokenConverter);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 配置client_id
.withClient("admin")
// 配置client_secret
.secret(passwordEncoder.encode("admin"))
// 配置访问token的有效期
.accessTokenValiditySeconds(3600)
// 配置刷新token的有效期
.refreshTokenValiditySeconds(864000)
// 自动授权配置(api调用需要打开此配置,不然一直都要手动确认授权,如果是)
.autoApprove(true)
// 配置redirect_uri,用于授权成功后的跳转
.redirectUris("http://localhost:9300/login")
// 配置申请的权限范围
.scopes("all")
// 配置grant_type,表示授权类型,
.authorizedGrantTypes("authorization_code", "password", "refresh_token");
}
/**
* 单点登录时必须验证
*
* @param security security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("isAuthenticated()");
}
}
网页单点服务验证
访问客户端需要授权的接口http://localhost:9300/user/getUser会跳转到授权服务的登录界面;
服务调用验证
autoApprove必须打开,不然服务调用无法通过授权
登录成功后,postMan会自动生成一个token放到Headers的Authorization中
最后请求成功
{
"authorities": [
{
"authority": "admin"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": "581EE1EE460FB72C2FD165393A0BC9B7",
"tokenValue": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ6Z2x4Iiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYzNTg3MDAwMSwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiODA0MTllOTgtZjA3My00Mjc4LTg0ZWItMjU1YWVjZjAzZTYwIiwiY2xpZW50X2lkIjoiYWRtaW4iLCJlbmhhbmNlIjoiZW5oYW5jZSBpbmZvIn0.wPmxNkAhtVPZNuhigaQYXIBaQigP7JPqtwVu2mCrHN8",
"tokenType": "bearer",
"decodedDetails": null
},
"authenticated": true,
"userAuthentication": {
"authorities": [
{
"authority": "admin"
}
],
"details": null,
"authenticated": true,
"principal": "zglx",
"credentials": "N/A",
"name": "zglx"
},
"oauth2Request": {
"clientId": "admin",
"scope": [
"all"
],
"requestParameters": {
"client_id": "admin"
},
"resourceIds": [
],
"authorities": [
],
"approved": true,
"refresh": false,
"redirectUri": null,
"responseTypes": [
],
"extensions": {
},
"grantType": null,
"refreshTokenRequest": null
},
"clientOnly": false,
"principal": "zglx",
"credentials": "",
"name": "zglx"
}```
# oauth2-client添加权限校验
需要给client添加一个配置,用来添加方法级角色权限校验
```java
package com.example.oauth2client.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @Description:
* @Author: Zlx
* @Date: 2021/10/24 10:28 下午
*/
@Configuration
@Order(101)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
新增角色权限验证接口
@GetMapping("/testRole")
@PreAuthorize("hasAuthority('admin')")
Object testRole(Authentication authentication) {
return "Has admin auth!";
}
如果postMan生成的token不是admin角色用户的,将返回如下信息