spring security Oauth2 jjwt入门示例

宗政海
2023-12-01

依赖

<modules>
    <module>authorization-server</module>
    <module>resource-server</module>
</modules>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-jwt</artifactId>
        <version>1.0.9.RELEASE</version>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR8</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

认证服务器

认证服务器的配置主要包括四点:启用授权服务、客户端详情服务、配置令牌的访问端点和令牌服务、配置令牌端点的安全约束

  1. 启用授权服务:用于标识当前服务是一个授权服务 — 在配置类中增加@EnableAuthorizationServer即可

  2. 客户端详情服务: 用于配置需要认证的客户端相关信息

    // 重写AuthorizationServerConfigurerAdapter类中的configure(ClientDetailsServiceConfigurer clients)方法(注意参数),并在此方法中配置客户端信息。
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
         @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory() // 客户端信息存储位置
                	// 客户端详情
                    .withClient("client") // 客户端ID
                    .secret(passwordEncoder.encode("878412")) // 客户端密码
                    .resourceIds("user") // 客户端允许访问的资源
                    .redirectUris("http://localhost:8082/test")// 回调地址
                    .scopes("all")// 授权范围
    //                .authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token") // 授权类型:授权码模式、密码模式、客户端模式、简化模式
                    .authorizedGrantTypes("authorization_code") // 授权类型。
                    .autoApprove(false);
            // 可以在and()后面配置另外一个客户端的信息.
        }
    }
    
  3. 配置令牌的访问端点和令牌服务

    // 在TokenConfig中创建令牌的存储策略(jwt令牌)
    @Configuration
    public class TokenConfig {
    
        private  String SIGNING_KEY="snail878412";
        @Bean
        public TokenStore tokenStore(){
            // 令牌存储策略:jwt方式
            return new JwtTokenStore(accessTokenConverter());
        }
        @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
            jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);
            return jwtAccessTokenConverter;
        }
    
    }
    
    // 
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
        
         @Autowired
        private ClientDetailsService clientDetailsService;
    
        @Autowired
        private TokenStore tokenStore;
        
        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;
        
        // 令牌管理服务
        public AuthorizationServerTokenServices tokenServices(){
            DefaultTokenServices services = new DefaultTokenServices();
            services.setClientDetailsService(clientDetailsService); // 客户端详情服务器(第2点中已经配好的),自动注入
            services.setSupportRefreshToken(true); // 支持刷新令牌
            services.setTokenStore(tokenStore); // 令牌存储方式(在TokenConfig中配置的)
            
            // 令牌增强
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
            //(在TokenConfig中配置的)
            tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter)); 
            services.setTokenEnhancer(tokenEnhancerChain);
            
            services.setAccessTokenValiditySeconds(7200); // 令牌过期时间
            services.setRefreshTokenValiditySeconds(259200); // 刷新令牌过期时间
            return services;
        }
        
        //授权码的存储方式
        @Bean
        public AuthorizationCodeServices authorizationCodeServices(){
            return new InMemoryAuthorizationCodeServices();
        }
        
        @Autowired
        private AuthorizationCodeServices authorizationCodeServices;
        
        @Autowired
        private AuthenticationManager authenticationManager; // 认证管理器自动注入
        
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // 令牌访问端点,包括:/authrize、/token、/confirm_access、/error、/check_token、/token_key(前缀为/oauth)
            endpoints
                    .authenticationManager(authenticationManager) // 认证管理器(参见后面的说明)
                    .authorizationCodeServices(authorizationCodeServices) // 授权码服务(参见上面authorizationCodeServices方法的定义)
                    .tokenServices(tokenServices()) // 令牌管理服务(见上面的方法定义)
                    .allowedTokenEndpointRequestMethods(HttpMethod.POST);
        }
    }
    

    认证管理器定义

    // 配置SecurityConfig ,并在此配置文件中重写WebSecurityConfigurerAdapter类中的authenticationManagerBean方法创建出AuthenticationManager对象,同时还需要配置UserDetailsService
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/login/**").permitAll()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin();
        }
    }
    // 实现 UserDetailsService
    @Service
    public class UserService implements UserDetailsService {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            String pwd = passwordEncoder.encode("123456");
            return new User(username, pwd,
                    AuthorityUtils.commaSeparatedStringToAuthorityList("admin,sysadmin"));
        }
    }
    
    
  4. 配置令牌端点的安全约束

    令牌的端点服务允许如何方法

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    	@Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security.tokenKeyAccess("permitAll()")
                    .checkTokenAccess("permitAll()")
                    .allowFormAuthenticationForClients();
        }
    }
    

    将客户端和授权码存储在数据库中

    sql

    DROP TABLE IF EXISTS `oauth_access_token`;CREATE TABLE `oauth_access_token` (  `token_id` varchar(255) DEFAULT NULL COMMENT '加密的access_token的值',  `token` longblob COMMENT 'OAuth2AccessToken.java对象序列化后的二进制数据',  `authentication_id` varchar(255) DEFAULT NULL COMMENT '加密过的username,client_id,scope',  `user_name` varchar(255) DEFAULT NULL COMMENT '登录的用户名',  `client_id` varchar(255) DEFAULT NULL COMMENT '客户端ID',  `authentication` longblob COMMENT 'OAuth2Authentication.java对象序列化后的二进制数据',  `refresh_token` varchar(255) DEFAULT NULL COMMENT '加密的refresh_token的值') ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    DROP TABLE IF EXISTS `oauth_approvals`;CREATE TABLE `oauth_approvals` (  `userId` varchar(255) DEFAULT NULL COMMENT '登录的用户名',  `clientId` varchar(255) DEFAULT NULL COMMENT '客户端ID',  `scope` varchar(255) DEFAULT NULL COMMENT '申请的权限范围',  `status` varchar(10) DEFAULT NULL COMMENT '状态(Approve或Deny)',  `expiresAt` datetime DEFAULT NULL COMMENT '过期时间',  `lastModifiedAt` datetime DEFAULT NULL COMMENT '最终修改时间') ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    DROP TABLE IF EXISTS `oauth_client_details`;CREATE TABLE `oauth_client_details` (  `client_id` varchar(255) NOT NULL COMMENT '客户端ID',  `resource_ids` varchar(255) DEFAULT NULL COMMENT '资源ID集合,多个资源时用逗号(,)分隔',  `client_secret` varchar(255) DEFAULT NULL COMMENT '客户端密匙',  `scope` varchar(255) DEFAULT NULL COMMENT '客户端申请的权限范围',  `authorized_grant_types` varchar(255) DEFAULT NULL COMMENT '客户端支持的grant_type',  `web_server_redirect_uri` varchar(255) DEFAULT NULL COMMENT '重定向URI',  `authorities` varchar(255) DEFAULT NULL COMMENT '客户端所拥有的Spring Security的权限值,多个用逗号(,)分隔',  `access_token_validity` int(11) DEFAULT NULL COMMENT '访问令牌有效时间值(单位:秒)',  `refresh_token_validity` int(11) DEFAULT NULL COMMENT '更新令牌有效时间值(单位:秒)',  `additional_information` varchar(255) DEFAULT NULL COMMENT '预留字段',  `autoapprove` varchar(255) DEFAULT NULL COMMENT '用户是否自动Approval操作') ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    DROP TABLE IF EXISTS `oauth_client_token`;CREATE TABLE `oauth_client_token` (  `token_id` varchar(255) DEFAULT NULL COMMENT '加密的access_token值',  `token` longblob COMMENT 'OAuth2AccessToken.java对象序列化后的二进制数据',  `authentication_id` varchar(255) DEFAULT NULL COMMENT '加密过的username,client_id,scope',  `user_name` varchar(255) DEFAULT NULL COMMENT '登录的用户名',  `client_id` varchar(255) DEFAULT NULL COMMENT '客户端ID') ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    DROP TABLE IF EXISTS `oauth_code`;CREATE TABLE `oauth_code` (  `code` varchar(255) DEFAULT NULL COMMENT '授权码(未加密)',  `authentication` blob DEFAULT NULL COMMENT 'AuthorizationRequestHolder.java对象序列化后的二进制数据') ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    DROP TABLE IF EXISTS `oauth_refresh_token`;CREATE TABLE `oauth_refresh_token` (  `token_id` varchar(255) DEFAULT NULL COMMENT '加密过的refresh_token的值',  `token` longblob COMMENT 'OAuth2RefreshToken.java对象序列化后的二进制数据 ',  `authentication` longblob COMMENT 'OAuth2Authentication.java对象序列化后的二进制数据') ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    DROP TABLE IF EXISTS `sys_user`;CREATE TABLE `sys_user` (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `username` varchar(50) DEFAULT NULL COMMENT '用户名',  `password` varchar(50) DEFAULT NULL COMMENT '密码',  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';
    

    增加依赖

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.3</version>
    </dependency>
    

    修改配置

    // 改用数据库存储的方式
    @Autowired
    private DataSource dataSource;
    
    // 客户端jdbc存储方式
    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService(){
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder());
        return jdbcClientDetailsService;
    }
    //授权码的存储方式(数据库)
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return  new JdbcAuthorizationCodeServices(dataSource);
    }
    
    // 修改客户端详情的配置(configure(ClientDetailsServiceConfigurer clients)方法)
    @Autowired
    private JdbcClientDetailsService jdbcClientDetailsService;
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 客户端详情
        clients.withClientDetails(jdbcClientDetailsService);
    }
    

资源服务器

@Configuration
public class TokenConfig {

    private  String SIGNING_KEY="snail878412";

    @Bean
    public TokenStore tokenStore(){
        // 令牌存储策略:jwt方式
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);
        return jwtAccessTokenConverter;
    }
}

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;
    
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
                .resourceId("user")
                .tokenStore(tokenStore)
                .stateless(true);
    }
}

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/test/**")
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll()
                .and()
                .csrf()
                .disable()
        ;
    }
}

资源

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("getCurrentUser")
    public Object getCurrentUser(Authentication authentication){
        return authentication.getPrincipal();
    }
}
 类似资料: