- Spring Security OAuth简介
- 实现标准的OAuth服务提供商
- 实现资源服务器
- 基本Token参数配置
- 使用Redis存储token
- 使用JWT替换默认Token
- 扩展JWT信息
- 令牌刷新
- 基于JWT实现SSO---单点登录
- 获取当前登陆用户信息
Spring Security OAuth简介
浏览器认证成功后,认证信息会存储在服务器的session中,以后再来访问的时候,通过jsessionid去验证session中是否存在认证信息,进而判断是否需要认证。
在现代应用中,后端往往不直接和浏览器交互。
如果客户端是APP,一般是不会在请求中带jsessionid的,而在前后端分离的架构中,浏览器把请求发送给Web服务器,由Web服务器发送请求到后段服务器中,此时也是不带jsessionid的。
APP认证与浏览器访问最大的区别,就是没有cookie,jsessionid等
当然,不是不能用cookie、jsessionid这种认证方式,只要模拟浏览器还是能办到的,但是会带来一些问题。
1、开发繁琐
2、安全性和客户体验差
一旦jsessionid被知道,就很危险。
3、有些前端技术不支持Cookie(微信小程序)
解决方案:访问服务器的时候每次都带上token(令牌)
应用服务器不再把用户信息存储再session中,而是根据每次用户请求带来的token,查询他是谁,他能干什么。
实现标准的OAuth服务提供商
开启认证服务器支持
package com.imooc.security.app;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
/**
* @Author 张柳宁
* @Description
* @Date Create in 2018/4/23
* @Modified By:
*/
@Configuration
@EnableAuthorizationServer//开启认证服务器支持
public class ImoocAuthorizationServerConfig {
}
系统内部已经实现了4种授权模式,使用注解开启后,我们就可以很方便的进行使用了。
启动服务后,会有几个url服务:
# 确认授权
Mapped "{[/oauth/authorize]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(java.util.Map<java.lang.String, java.lang.Object>,java.util.Map<java.lang.String, java.lang.String>,org.springframework.web.bind.support.SessionStatus,java.security.Principal)
Mapped "{[/oauth/authorize],methods=[POST],params=[user_oauth_approval]}" onto public org.springframework.web.servlet.View org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.approveOrDeny(java.util.Map<java.lang.String, java.lang.String>,java.util.Map<java.lang.String, ?>,org.springframework.web.bind.support.SessionStatus,java.security.Principal)
# 用授权码获取token
Mapped "{[/oauth/token],methods=[GET]}" onto public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.OAuth2AccessToken> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.getAccessToken(java.security.Principal,java.util.Map<java.lang.String, java.lang.String>) throws org.springframework.web.HttpRequestMethodNotSupportedException
Mapped "{[/oauth/token],methods=[POST]}" onto public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.OAuth2AccessToken> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(java.security.Principal,java.util.Map<java.lang.String, java.lang.String>) throws org.springframework.web.HttpRequestMethodNotSupportedException
Mapped "{[/oauth/check_token]}" onto public java.util.Map<java.lang.String, ?> org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint.checkToken(java.lang.String)
Mapped "{[/oauth/confirm_access]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint.getAccessConfirmation(java.util.Map<java.lang.String, java.lang.Object>,javax.servlet.http.HttpServletRequest) throws java.lang.Exception
Mapped "{[/oauth/error]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.WhitelabelErrorEndpoint.handleError(javax.servlet.http.HttpServletRequest)
在日志中还看到了
security.oauth2.client.client-id = ec70102d-b767-4c97-8b48-0537edd95132
security.oauth2.client.client-secret = fc6c5a6b-c1eb-45dc-897a-0984b7285f71
这两个参数也是有用的
为了防止每次都是一个随机生成的值,也可以自己配置
security:
basic:
# 开启 Spring Security
enabled: true
oauth2:
client:
client-id: imooc
client-secret: imooc
获取授权码 /oauth/authorize
参数
- response_type=code
- client_id=ec70102d-b767-4c97-8b48-0537edd95132
- redirect_uri=http://www.baidu.com
这个参数是可选的,只是为了看到授权码
- scope=all
完整的请求:http://localhost:8080/oauth/authorize?response_type=code&client_id=ec70102d-b767-4c97-8b48-0537edd95132&redirect_uri=http://example.com&scope=all
image.png
在这里,认证服务器扮演了腾讯的角色,client_id扮演了第三方应用的角色。
而这个用户名,是说client_id要申请腾讯哪个用户的权限。而请求这个用户的什么权限,是通过请求种的scope
参数决定的.
scope
是由服务提供商,如:腾讯定义的。
这个也很好理解。一个用户有哪些权限,需要划分出哪些权限,当然由提供用户服务的服务提供商是最清楚的。
这里的用户名密码校验由我们的 UserDetailsService 接口实现来定。
image.png这个是授权页面
image.png这个code值就是授权码
UserDetailService
package com.imooc.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.social.security.SocialUser;
import org.springframework.social.security.SocialUserDetails;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.stereotype.Component;
/**
* 自定义认证逻辑
*/
@Component
public class DemoUserDetailsService implements UserDetailsService, SocialUserDetailsService {
private Logger logger = LoggerFactory.getLogger(DemoUserDetailsService.class);
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("查找登陆用户名:{}", username);
//模拟根据用户名查找用户信息
return buildUser(username);
}
@Override
public SocialUserDetails loadUserByUserId(String id) throws UsernameNotFoundException {
logger.info("查找用户ID:{}", id);
//模拟根据用户名查找用户信息
return buildUser(id);
}
private SocialUserDetails buildUser(String userId) {
// 根据用户名查找用户信息
//根据查找到的用户信息判断用户是否被冻结
String password = passwordEncoder.encode("123456");
logger.info("数据库密码是:" + password);
return new SocialUser(userId, password,
true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER"));
}
}
注意,必须有 ROLE_USER 角色
获取令牌 /oauth/token
授权码模式
这是个post请求
image.pngcode值就是上一个请求返回的,跳转到url后面带着的code参数值
url:http://localhost:8080/oauth/token
返回
{
"access_token": "785ffd11-3958-49e9-b60a-464faace3e34",
"token_type": "bearer",
"refresh_token": "72b0b0df-b1b2-4847-8be8-0ccda432ff6f",
"expires_in": 43199,
"scope": "all"
}
用户名密码模式
image.png和授权码模式相比,就是请求的参数不同,一个是通过授权码获取token,一个是通过用户名密码获取token
在用户名密码模式下,实际上用户是把在服务提供商(如腾讯)中的用户名密码告诉了第三方,不是很安全。
用户名密码模式适用于 服务提供商和第三方调用方是同一家公司产品的情况。
不管是哪种模式,对于同一个用户,如果token没有过期,那么获取到的token值是不会变的。
实现资源服务器
在上一节中,我们实现了一个认证服务器,可通过授权码模式或密码模式,从认证服务器上获取token。
package com.imooc.security.app;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
/**
* @Author 张柳宁
* @Description
* @Date Create in 2018/4/23
* @Modified By:
*/
@Configuration
@EnableResourceServer//开启资源服务器支持
public class ImoocResourceServerConfig {
}
然后所有的Rest服务在访问前,就需要配置token
如果直接访问的话,
image.png
我们按照前面的,不管是授权码模式还是用户名密码模式获取到token,然后
http://localhost:8080/user?access_token=e756384d-bf52-4799-bda8-c6933ccbb031
把token值加在后面作为access_token参数,就可以正常访问了。
另一种传递token的方式是在头部
我觉得这种方式反而更常见,通用一点。
第一种传递token的方式第一是会暴露在url上,第二是只能get方式传递。
目前存在的一些问
1、token存在内存中
2、只能用户名密码获取token,不能使用如发送个短信就获取token这种需求
3、token值如何定制
...
基本Token参数配置
package com.imooc.security.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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;
/**
* @Author 张柳宁
* @Description
* @Date Create in 2018/4/23
* @Modified By:
*/
@Configuration
@EnableAuthorizationServer//开启认证服务器支持
public class ImoocAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()//配置内存中,也可以是数据库
.withClient("clientId-imooc")//clientid
.secret("clientSecret-imooc")
.accessTokenValiditySeconds(3600)//token有效时间 秒
.authorizedGrantTypes("refresh_token", "pasword", "authorization_code")//token模式
.scopes("all", "read", "write")//限制允许的权限配置
.and()//下面配置第二个应用 (不知道动态的是怎么配置的,那就不能使用内存模式,应该使用数据库模式来吧)
.withClient("test")
.scopes("testSc")
.accessTokenValiditySeconds(7200)
.scopes("all");
}
}
如何通过配置,动态识别配置的多个客户端呢?
在yaml中,除了配置基本类型外,也可以是数组
a:
b:
c[0]:
p1: 111
p2: 222
c[1]:
p1: 233
p2: 444
c就是数组对象,p1、p2就是数组对象元素的成员变量。
在configure中读取配置文件后动态配置即可。
注意,如果设置token的过期时间位0,就相当于永远不会过期。
这个要注意一下
使用Redis存储token
package com.imooc.security.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
/**
* @Author 张柳宁
* @Description
* @Date Create in 2018/4/23
* @Modified By:
*/
@Configuration
public class TokenStoreConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
}
package com.imooc.security.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.provider.token.TokenStore;
/**
* @Author 张柳宁
* @Description
* @Date Create in 2018/4/23
* @Modified By:
*/
@Configuration
@EnableAuthorizationServer//开启认证服务器支持
public class ImoocAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenStore redisTokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(redisTokenStore)//使用Redis作为Token的存储
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()//配置内存中,也可以是数据库
.withClient("clientId-imooc")//clientid
.secret("clientSecret-imooc")
.accessTokenValiditySeconds(3600)//token有效时间 秒
.authorizedGrantTypes("refresh_token", "password", "authorization_code")//token模式
.scopes("all", "read", "write")//限制允许的权限配置
.and()//下面配置第二个应用 (不知道动态的是怎么配置的,那就不能使用内存模式,应该使用数据库模式来吧)
.withClient("test")
.scopes("testSc")
.accessTokenValiditySeconds(7200)
.scopes("all");
}
}
使用JWT替换默认Token
使用token方式的认证存在一个特点,就是其依赖存储,如果Redis挂了,token机制也就用不了了。
默认的token本身只是个uuid,是不包含任何信息的。
JWT-Json Web Token特点:
- 是自包含的
- 密签
- 可扩展
配置
package com.imooc.security.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
/**
* @Author 张柳宁
* @Description
* @Date Create in 2018/4/23
* @Modified By:
*/
@Configuration
public class TokenStoreConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
//imooc.security.oauth2.storeType=redis时生效
@Bean
@ConditionalOnProperty(prefix = "imooc.security.oauth2", name = "storeType", havingValue = "redis")
public TokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
//imooc.security.oauth2.storeType=jwt时生效,如果没有配置此项,那么也是生效的
@Configuration
@ConditionalOnProperty(prefix = "imooc.security.oauth2", name = "storeType", havingValue = "jwt", matchIfMissing = true)
public static class JwtTokenConfig {
@Bean
public TokenStore jetTokenStroe() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey("signingjkey");//密钥,放到配置文件中
return jwtAccessTokenConverter;
}
}
}
package com.imooc.security.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
/**
* @Author 张柳宁
* @Description
* @Date Create in 2018/4/23
* @Modified By:
*/
@Configuration
@EnableAuthorizationServer//开启认证服务器支持
public class ImoocAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired(required = false)
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenStore redisTokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(redisTokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
if (jwtAccessTokenConverter != null) {
endpoints.accessTokenConverter(jwtAccessTokenConverter);
}
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()//配置内存中,也可以是数据库
.withClient("clientId-imooc")//clientid
.secret("clientSecret-imooc")
.accessTokenValiditySeconds(3600)//token有效时间 秒
.authorizedGrantTypes("refresh_token", "password", "authorization_code")//token模式
.scopes("all", "read", "write")//限制允许的权限配置
.and()//下面配置第二个应用 (不知道动态的是怎么配置的,那就不能使用内存模式,应该使用数据库模式来吧)
.withClient("test")
.scopes("testSc")
.accessTokenValiditySeconds(7200)
.scopes("all");
}
}
配置完后,我们使用密码模式发送请求
image.png
我们发现,token和refresh_token就不再是uuid了。
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjQ1NzkyMjAsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iLCJST0xFX1VTRVIiXSwianRpIjoiYTA5YmFkZjUtYWE2ZC00NDAwLTgxY2YtMDkxYzQ5ZDEyZWY5IiwiY2xpZW50X2lkIjoiY2xpZW50SWQtaW1vb2MiLCJzY29wZSI6WyJhbGwiXX0.LtL1kcDY-396cf_o1keujKIEpDQj6BvVd5znX-ICMHI",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJhMDliYWRmNS1hYTZkLTQ0MDAtODFjZi0wOTFjNDlkMTJlZjkiLCJleHAiOjE1MjcxNjc2MjAsImF1dGhvcml0aWVzIjpbImFkbWluIiwiUk9MRV9VU0VSIl0sImp0aSI6IjQ5MThjZTFmLTQyNDktNDEyOS1hZjYwLTBlMDcxNThlNjBkMyIsImNsaWVudF9pZCI6ImNsaWVudElkLWltb29jIn0.5wfm4E2HQBZH94HaaDD_p7vr4FVtRJ1Et7F0Oo7pKqM",
"expires_in": 3599,
"scope": "all",
"jti": "a09badf5-aa6d-4400-81cf-091c49d12ef9"
}
对于token的含义,我们可以在 jwt.io 网站上进行解码
image.png相当于我们服务器拿到jwt形式的token后,只需要解码,就能够知道用户登陆信息,不需要去redis等存储服务器中去查找。
使用方式与默认的token一样,在请求头中添加
Authorization=bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjQ1ODAzMjYsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iLCJST0xFX1VTRVIiXSwianRpIjoiNzgxZjhkN2YtNGQ5NS00YzczLWI4MGEtYTYzYzBjMDBkN2VjIiwiY2xpZW50X2lkIjoiY2xpZW50SWQtaW1vb2MiLCJzY29wZSI6WyJhbGwiXX0.qVsrWRZvTw22vJQ_H-0wB1myRCvWCIQsI4iZuUq3UJQ
即可
扩展JWT信息
这里的扩展,指的是可以在发放token的时候,添加自定义字段返回给调用方,而且调用房拿到的token中,会返回这个字段。
- ImoocJwtTokenEnhancer
package com.imooc.security.app.jwt;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
/**
* @Author 张柳宁
* @Description
* @Date Create in 2018/4/24
* @Modified By:
*/
public class ImoocJwtTokenEnhancer implements TokenEnhancer{
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String,Object> info = new HashMap<>();
info.put("author","张柳宁");
((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info);
return accessToken;
}
}
- TokenStoreConfig
@Bean
public TokenEnhancer jwtTokenEnhancer() {
return new ImoocJwtTokenEnhancer();
}
- ImoocAuthorizationServerConfig
@Autowired(required = false)
private TokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(redisTokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
if (jwtAccessTokenConverter != null
&& jwtTokenEnhancer != null) {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancers = new ArrayList<>();
enhancers.add(jwtTokenEnhancer);
enhancers.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(enhancers);
endpoints
.tokenEnhancer(enhancerChain)
.accessTokenConverter(jwtAccessTokenConverter);
}
}
令牌刷新
我们一般还是会设置令牌有效时间的,如果令牌过期了就让用户重新登陆,这个体验很不好。
在我们先前获取token的时候,都有一个refresh_token,这个就是用来刷新token的。
对于客户端来说可以这么使用:当获取到token的时候,查看过期时间还剩下多少,一看所剩时间不多,马上刷新,获取新的token,然后再发起调用。
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()//配置内存中,也可以是数据库
.withClient("clientId-imooc")//clientid
.secret("clientSecret-imooc")
.accessTokenValiditySeconds(3600)//token有效时间 秒
.refreshTokenValiditySeconds(3600*24*30)//refresh_token有效时间
.authorizedGrantTypes("refresh_token", "password", "authorization_code")//token模式
.scopes("all", "read", "write")//限制允许的权限配置
.and()//下面配置第二个应用 (不知道动态的是怎么配置的,那就不能使用内存模式,应该使用数据库模式来吧)
.withClient("test")
.scopes("testSc")
.accessTokenValiditySeconds(7200)
.scopes("all");
}
refresh_token的有效时间,我们可以设置得久一点。
基于JWT实现SSO---单点登录
什么是单点登陆?
我们访问淘宝后登陆,然后跳转到天猫,发现也已经用淘宝的账号进行了登陆。
就是说一个账号同时在多个站点中有效,且不需要重复登陆,这就是单点登陆。
原理
获取当前登陆用户信息
获取普通认证方式的用户
不管表单认证,短信认证,token认证,只要上使用Spring Security框架进行的认证,都可以使用下面的方法获取当前登陆的用户
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println(ReflectionToStringBuilder.toString(userDetails, ToStringStyle.MULTI_LINE_STYLE));
System.out.println(userDetails.getAuthorities());
另一种方式
@GetMapping("/me")
public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails){
return userDetails;
}
获取JWT认证方式的用户
如果使用的是JWT,那么获取当前用户的方式为
@GetMapping("/me2")
public Object getCurrentUser2(Authentication user){
return user;
}
返回是一个json串
{
"authorities": [
{
"authority": "admin"
},
{
"authority": "ROLE_USER"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": null,
"tokenValue": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjQ1ODAzMjYsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iLCJST0xFX1VTRVIiXSwianRpIjoiNzgxZjhkN2YtNGQ5NS00YzczLWI4MGEtYTYzYzBjMDBkN2VjIiwiY2xpZW50X2lkIjoiY2xpZW50SWQtaW1vb2MiLCJzY29wZSI6WyJhbGwiXX0.qVsrWRZvTw22vJQ_H-0wB1myRCvWCIQsI4iZuUq3UJQ",
"tokenType": "bearer",
"decodedDetails": null
},
"authenticated": true,
"userAuthentication": {
"authorities": [
{
"authority": "admin"
},
{
"authority": "ROLE_USER"
}
],
"details": null,
"authenticated": true,
"principal": "admin",
"credentials": "N/A",
"name": "admin"
},
"clientOnly": false,
"principal": "admin",
"oauth2Request": {
"clientId": "clientId-imooc",
"scope": [
"all"
],
"requestParameters": {
"client_id": "clientId-imooc"
},
"resourceIds": [
],
"authorities": [
],
"approved": true,
"refresh": false,
"redirectUri": null,
"responseTypes": [
],
"extensions": {
},
"grantType": null,
"refreshTokenRequest": null
},
"credentials": "",
"name": "admin"
}
输出
org.springframework.security.core.userdetails.User@2d3ae2b[
password=<null>
username=admin
authorities=[ROLE_USER, admin]
accountNonExpired=true
accountNonLocked=true
credentialsNonExpired=true
enabled=true
]
[ROLE_USER, admin]
获取JWT用户及其扩展字段
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
引入依赖支持,用于解析JWT
@GetMapping("/me2")
public Object getCurrentUser2(Authentication user, HttpServletRequest request) throws UnsupportedEncodingException {
String header = request.getHeader("Authorization");
String token = StringUtils.substringAfter(header, "bearer ");
Claims claims = Jwts.parser().setSigningKey("signingjkey".getBytes("UTF-8")).parseClaimsJws(token).getBody();
String author = (String) claims.get("author");
System.out.println("Author:" + author);
return user;
}
注意,一定要在获取密签的时候指定编码,这里的author就是我们的自定义字段
有需要spring security视频教程的同学,加我微信 。备注 来自简书
微信号
网友评论
"需要进行身份认证"的弹框,输入什么都是重新弹出一样的弹框,不知该怎么解决,我的
springboot版本为2.0.2