zoukankan      html  css  js  c++  java
  • SpringSecurityOAuth2之JWT应用示例

      本文记录一下使用SpringSecurityOAuth2配置JWT的步骤

      1、相关知识

      OAuth协议简介:https://www.cnblogs.com/javasl/p/13054133.html

      OAuth 2.0官网:https://oauth.net/2/

      使用SpringSecurityOAuth2默认实现OAuth2授权示例:https://www.cnblogs.com/javasl/p/13060284.html

      使用SpringSecurityOAuth2配置自定义Token实现OAuth2授权示例:https://www.cnblogs.com/javasl/p/13068613.html

      2、JWT基础

      JWT全称是Json Web Token,它是JSON的一个开放的Token标准。JWT有三个特点:

      1)自包含。JWT中包含着Token有意义的信息,拿到Token,解析后就能知道里面包含的信息是什么,而Spring默认生成的Token是UUID,没有任何有意义的信息。它的信息需要根据这个Token去Redis中读取。

      2)密签。发出去的令牌不包含密码等敏感信息,使用指定的秘钥签名。

      3)可扩展。包含的信息可以根据业务需求自己定义。

      2、构建项目

      本文使用的springboot版本是2.0.4.RELEASE,不同版本可能会有所区别。下面是主要的配置文件和类:

      1)pom依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>2.1.3.RELEASE</version>
    </dependency>

      2)application.properties

    #不需要,暂时写死在代码中,重构时移植到此处即可

      3)主配置类

    @EnableWebSecurity
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
        
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.httpBasic().and().csrf().disable();
        }
        @Bean("authenticationManager")
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }

      4)用户认证类

    @Component
    public class MyUserDetailsService implements UserDetailsService{
    
        @Autowired
        private PasswordEncoder passwordEncoder;
        
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            System.out.println("登录用户名:"+username);
            String  password = passwordEncoder.encode("123456");
            return new User(username,password,true,true,true,true,
                    AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER"));
        }
    }

      5)认证服务类

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
    
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private UserDetailsService userDetailsService;
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Autowired
        private TokenStore tokenStore;
        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .tokenStore(tokenStore)//如果使用RedisTokenStore,则会把Token存入redis中,否则存在内存 .accessTokenConverter(jwtAccessTokenConverter) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .inMemory()//Token保存在内存中 .withClient("MyProject").secret(passwordEncoder.encode("MyProject_123"))//指明client-id和client-secret .accessTokenValiditySeconds(7200)//令牌有效时间,单位秒 .authorizedGrantTypes("refresh_token","password","authorization_code")//支持刷新令牌、密码模式、授权码模式 .scopes("all","read","write")//权限有哪些,如果这两配置了该参数,客户端发请求可以不带参数,使用配置的参数 .redirectUris("http://127.0.0.1:8080/login"); } }

      6)JWT配置类

    @Configuration
    public class JwtTokenConfig {
            
        @Bean
        public TokenStore jwtTokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() {
            JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
            accessTokenConverter.setSigningKey("shxiang");//设置秘钥
            return accessTokenConverter;
        }
    }

      说明:上一篇讲到RedisTokenStore,这里又有JwtTokenStore,一定要区分开。

      a)如果TokenStore的注入类型是RedisTokenStore,则生成的JWT会存入redis中。

      b)如果TokenStore的注入类型是JwtTokenStore,或者endpoints不设置TokenStore,那么生成的JWT在内存中。

      7)启动类

    @SpringBootApplication
    public class App {
        public static void main(String[] args) {
            SpringApplication.run(App.class, args);
        }
    }

      4、测试验证

      1)获取Token成功

      

      

      2)将Token复制到https://www.jsonwebtoken.io/解析Token成功 

      

      3)根据Token获取当前用户信息

    @GetMapping("/me")
    public Object getCurrentUser(Authentication user) {
        return user;
    }

      

      

      说明:上面是密码模式的测试,授权码模式的测试请参考上一篇的测试验证部分。

      5、扩展自定义字段 

      1)添加Token增强类

    public class MyJwtTokenEnhancer implements TokenEnhancer{
    
        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
            Map<String,Object> info = new HashMap<String, Object>();
            info.put("company","test test test");//拓展的字段
            ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info);
            return accessToken;
        }
    }

      2)修改JwtTokenConfig.java,添加jwtTokenEnhancer方法创建bean

    @Bean
    @ConditionalOnMissingBean(name = "jwtTokenEnhancer")
    public TokenEnhancer jwtTokenEnhancer() {
        return new MyJwtTokenEnhancer();
    }

      3)修改AuthorizationServerConfig.java的configure方法,如下:

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
        ... ...
        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;
        @Autowired
        private TokenEnhancer jwtTokenEnhancer;
        
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                 .authenticationManager(authenticationManager)
                 .userDetailsService(userDetailsService);
            
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancers = new ArrayList<>();
            enhancers.add(jwtTokenEnhancer);
            enhancers.add(jwtAccessTokenConverter);
            enhancerChain.setTokenEnhancers(enhancers);
            
            endpoints.tokenEnhancer(enhancerChain);
            endpoints.accessTokenConverter(jwtAccessTokenConverter);
        }
        ... ...
    }

      测试结果如下:

      

      

      

      注意:拓展的字段不会封装到用户信息Authentication中,执行http://localhost:8080/me不会获取到拓展字段。

      6、获取自定义字段

      1)添加pom.xml依赖

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.7.0</version>
    </dependency>

      2)测试方法如下

    @GetMapping("/me")
    public Object getCurrentUser(Authentication user,HttpServletRequest request) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException, UnsupportedEncodingException {
          String header = request.getHeader("Authorization");
          String token = StringUtils.substringAfter(header,"bearer ");
          Claims claims = Jwts.parser().setSigningKey("shxiang".getBytes("UTF-8"))//shxiang是秘钥
                    .parseClaimsJws(token).getBody();
          String company = (String)claims.get("company");
          System.out.println("-------company----------:"+company);
          return user;
    }

      结果如下:

      

      

      7、Token的刷新

      获取令牌的时候会获取access_token和refresh_token,如下图所示:

      

      使用refresh_token可以刷新Token,如下:

      

      

      当用户使用Token访问时,发现超时了,在无感知的情况下立马使用refresh_token请求一个新的Token。refresh_token设置较长时间,如:

    .accessTokenValiditySeconds(7200)
    .refreshTokenValiditySeconds(2592000)
  • 相关阅读:
    微信小程序入门
    rem js相关
    移动端rem.js使用方法
    手机访问PC网站自动跳转到手机版
    当 return 遇到 try
    Ubuntu apt 使用代理
    shell 十进制数字转十六进制字符串并将结果保存到变量
    (二)一起学 Java Collections Framework 源码之 AbstractCollection
    (一)一起学 Java Collections Framework 源码之 概述
    解决 meld 出现 locale.setlocale(locale.LC_ALL,'') 失败的问题
  • 原文地址:https://www.cnblogs.com/javasl/p/13122273.html
Copyright © 2011-2022 走看看