Javajava web

06 Spring Security OAuth开发APP认证框

2018-04-23  本文已影响467人  Koko_滱滱

Spring Security OAuth简介

浏览器认证成功后,认证信息会存储在服务器的session中,以后再来访问的时候,通过jsessionid去验证session中是否存在认证信息,进而判断是否需要认证。

image.png
在现代应用中,后端往往不直接和浏览器交互。
如果客户端是APP,一般是不会在请求中带jsessionid的,而在前后端分离的架构中,浏览器把请求发送给Web服务器,由Web服务器发送请求到后段服务器中,此时也是不带jsessionid的。

APP认证与浏览器访问最大的区别,就是没有cookie,jsessionid等
当然,不是不能用cookie、jsessionid这种认证方式,只要模拟浏览器还是能办到的,但是会带来一些问题。

1、开发繁琐
2、安全性和客户体验差
一旦jsessionid被知道,就很危险。
3、有些前端技术不支持Cookie(微信小程序)

image.png

解决方案:访问服务器的时候每次都带上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.png

code值就是上一个请求返回的,跳转到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的方式是在头部

image.png
我觉得这种方式反而更常见,通用一点。
第一种传递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中读取配置文件后动态配置即可。

image.png

注意,如果设置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中,会返回这个字段。

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;
    }
}

        @Bean
        public TokenEnhancer jwtTokenEnhancer() {
            return new ImoocJwtTokenEnhancer();
        }
    @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的。

image.png

对于客户端来说可以这么使用:当获取到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---单点登录

什么是单点登陆?
我们访问淘宝后登陆,然后跳转到天猫,发现也已经用淘宝的账号进行了登陆。

就是说一个账号同时在多个站点中有效,且不需要重复登陆,这就是单点登陆。

原理

image.png

获取当前登陆用户信息

获取普通认证方式的用户

不管表单认证,短信认证,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视频教程的同学,加我微信 。备注 来自简书


微信号
上一篇下一篇

猜你喜欢

热点阅读