SpringSecurity之令牌配置

基于Oauth2发放token,在请求头中携带token作为用户身份鉴定

Token参数配置

  • 定义可发放令牌的应用信息

    package com.cong.security.core.properties;
    import lombok.Data;
    @Data
    public class OAuth2ClientProperties {
    	//应用id
    	private String clientId;
    	//密码
    	private String clientSecret;
    	//token过期时间
    	private int accessTokenValiditySeconds;
    	//支持类型
    	private String[] authorizedGrantTypes;
    	//权限类型
    	private String[] scopes;
    }
    

    在OAuth2Properties类中添加private OAuth2ClientProperties[] clients = {};
    yml配置文件添加配置:
    在这里插入图片描述

  • 修改MyAuthorizationServerConfig配置

    package com.cong.security.app.authentication;
    
    import com.cong.security.core.properties.OAuth2ClientProperties;
    import com.cong.security.core.properties.SecurityProperties;
    import org.apache.commons.lang3.ArrayUtils;
    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.builders.InMemoryClientDetailsServiceBuilder;
    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;
    
    @Configuration
    @EnableAuthorizationServer
    public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired(required = false)
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Autowired
        private SecurityProperties securityProperties;
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager)
                    .userDetailsService(userDetailsService);
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 可以给哪些应用发放令牌
            // 可以存储在内存中,也可以存储在数据库中JdbcClientDetailsServiceBuilder----->clients.jdbc(dataSource)
            InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
            if (ArrayUtils.isNotEmpty(securityProperties.getOAuth2().getClients())) {
                for (OAuth2ClientProperties config : securityProperties.getOAuth2().getClients()) {
                    builder.withClient(config.getClientId()).secret(config.getClientSecret())// 设置client-id以及client-secret
                            .accessTokenValiditySeconds(config.getAccessTokenValiditySeconds())// 设置token过期时间,单位S
                            .refreshTokenValiditySeconds(config.getAccessTokenValiditySeconds() * 3) // 刷新token过期时间是token自身的三倍
                            .authorizedGrantTypes(config.getAuthorizedGrantTypes())// 支持的授权模式
                            .scopes(config.getScopes());// 可以发出去的权限
                }
            }
        }
    }
    
  • 控制令牌存储
    APP模块下不存在Session,用户一次登录永久有效,而服务端需要经常性的进行版本迭代,服务中断,让用户重新登陆明显不合适,方法是将令牌存储到持久化存储里面(数据库、Redis),后面会使用无状态Token,即服务端不对Token进行存储。
    使用Redis进行存储:
    在这里插入图片描述
    进行如上配置之后执行登录操作,在对应的Redis数据库中即可查看相应的数据:
    在这里插入图片描述

JWT替换默认Token

  • JWT
    自包含:可以包含相关的用户信息,最基本的数据关联,用户执行相关操作时需要有对应的权限,此时请求头中会携带Token,对Token进行解析就可以获取自己定义的相关信息,例如userId,而不必根据Token再去查询相关联的数据。
    密签:签名用于验证消息在此过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证JWT的发送者是它所说的真实身份。
    可扩展:可以自定义相关参数

  • 代码
    修改TokenStoreConfig

    package com.cong.security.app.social;
    
    import javax.sql.DataSource;
    import com.cong.security.core.properties.SecurityProperties;
    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.JdbcTokenStore;
    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;
    
    @Configuration
    public class TokenStoreConfig {
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;
        @Autowired
        private DataSource dataSource;
    
        //只有在配置文件中存在pet.security.oAuth2.storeType配置,且值为jdbc时当前配置生效
        @Bean
        @ConditionalOnProperty(prefix = "my.security.oAuth2", name = "storeType", havingValue = "jdbc")
        public TokenStore jdbcTokenStore() {
            return new JdbcTokenStore(dataSource);
        }
    
        //只有在配置文件中存在pet.security.oAuth2.storeType配置,且值为redis时当前配置生效
        @Bean
        @ConditionalOnProperty(prefix = "my.security.oAuth2", name = "storeType", havingValue = "redis")
        public TokenStore redisTokenStore() {
            return new RedisTokenStore(redisConnectionFactory);
        }
    
        //只有在配置文件中存在pet.security.oAuth2.storeType配置,且值为jwt时当前配置生效
        @Configuration
        @ConditionalOnProperty(prefix = "my.security.oAuth2", name = "storeType", havingValue = "jwt", matchIfMissing = true)
        public static class JwtTokenConfig {
            @Autowired
            private SecurityProperties securityProperties;
    
            //处理token存储
            @Bean
            public TokenStore jwtTokenStore() {
                return new JwtTokenStore(jwtAccessTokenConverter());
            }
    
            //处理token生成逻辑
            @Bean
            public JwtAccessTokenConverter jwtAccessTokenConverter() {
                JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
                // 设置签名密钥
                accessTokenConverter.setSigningKey(securityProperties.getOAuth2().getJwtSigningKey());
                return accessTokenConverter;
            }
        }
    }
    

    修改MyAuthorizationServerConfig在这里插入图片描述
    此时执行登录操作返回的Token为JWT生成的Token:
    在这里插入图片描述
    https://www.jsonwebtoken.io/网站上进行解析
    在这里插入图片描述

扩展及解析Token

  • 自定义增强器

    package com.cong.security.app.social;
    
    import java.util.HashMap;
    import java.util.Map;
    import com.cong.security.core.social.MySocialUser;
    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;
    
    public class MyJwtTokenEnhancer implements TokenEnhancer {
    
    	@Override
    	public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
    		Map<String, Object> map = new HashMap<>();
    		MySocialUser mySocialUser = (MySocialUser) authentication.getPrincipal();
    		// 从数据库中根据手机号之类信息查询出
    		map.put("userId", mySocialUser.getId());
    		((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
    		return accessToken;
    	}
    }
    
  • 配置到JwtTokenConfig中
    在这里插入图片描述

  • 修改MyAuthorizationServerConfig配置类
    在这里插入图片描述
    此时登录接口返回token参数中包含自定义参数
    在这里插入图片描述

  • 解析Token
    添加依赖(在login模块):

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>${jjwt.version}</version>
    </dependency>
    

    编写token解析类:

    package com.cong.security.config;
    
    import javax.servlet.http.HttpServletRequest;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class LoginUser {
    
    	/**
    	 * 从token中解析出当前账号标识
    	 */
    	public static String getUserId() {
    		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    		HttpServletRequest request = attributes.getRequest();
    		String header = request.getHeader("Authorization");
    		String token = StringUtils.substringAfter(header, "bearer ");
    		Claims claims = null;
    		// 用户在系统内标识
    		String userId = null;
    		try {
    			claims = Jwts.parser().setSigningKey("mydev".getBytes("UTF-8")).parseClaimsJws(token).getBody();
    			userId = (String) claims.get("userId");
    		} catch (Exception e) {
    			userId = null;
    		}
    		return userId;
    	}
    }
    

    随便提供一个接口:
    在这里插入图片描述
    使用登录接口返回的token作为请求头请求上面的接口即可返回当前用户对应的用户标识,解析Token成功。

  • Token刷新
    当用户Token失效之后无需用户重新登录,可以使用认证时返回的refresh_token获取新的有效Token,新的Token中用户身份有效期都是全新的,可以使用在用户角色变更或者用户账号锁死等情况中。
    如果出现token刷新报错401,查看文章:SpringSecurity升级之后token刷新接口401

发布了43 篇原创文章 · 获赞 25 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/single_cong/article/details/104228206