OAuth2.0认证服务搭建记录

配置OAuth2.0

代码结构
在这里插入图片描述

1、依赖准备

        <!--spring security的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--OAuth2的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>

        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

2、编写配置类

1、令牌配置类
package team.lcf.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * @program: LuoThinking-cloud
 * @description: 令牌相关配置类
 * @author: Cheng Zhi
 * @create: 2022-05-25 18:29
 **/
@Configuration
public class AccessTokenConfig {

    /**
     * todo 暂时写死,后续需要配置到文件中
     */
    private String SINGING_KEY = "czz";

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 设置秘钥
        // todo 后面改成非对称加密
        converter.setSigningKey(SINGING_KEY);
        System.out.println("--------" + SINGING_KEY);
        return converter;
    }
}

2、认证服务器配置(注意这里如果指定数据库模式,需要依赖spring-boot-starter-jdbc)
package team.lcf.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;

/**
 * @program: LuoThinking-cloud
 * @description: 授权服务
 * @author: Cheng Zhi
 * @create: 2022-05-25 18:06
 **/
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 密码模式需要的认证管理器
     */
    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    public AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private DataSource dataSource;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.tokenKeyAccess("permitAll()")       // /oauth/token_key 是公开的
                .checkTokenAccess("permitAll()")     // /oauth/check_token公开
                .allowFormAuthenticationForClients(); // 表单认证
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.withClientDetails(clientDetailsService);
/*        // todo 暂时写死,之后要放到数据库
        clients.inMemory()
                .withClient("password")
                .authorizedGrantTypes("authorization_code", "implicit", "client_credentials", "password", "refresh_token")
                // 过期时间
                //.accessTokenValiditySeconds(1800)
                .resourceIds("rid")
                .scopes("all")
                .secret(new BCryptPasswordEncoder().encode("123"));*/

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
                .authenticationManager(authenticationManager)         // 认证管理器
                .userDetailsService(userDetailsService)               // 用户信息
                .authorizationCodeServices(authorizationCodeServices) // 授权码管理器
                .tokenServices(tokenServices())                       // 令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET)
                .pathMapping("/oauth/token","/login"); // 这里自定义获取令牌路径
    }

    /**
     * 令牌管理服务的配置
     */
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        //客户端端配置策略
        services.setClientDetailsService(clientDetailsService);
        //支持令牌的刷新
        services.setSupportRefreshToken(true);
        //令牌服务
        services.setTokenStore(tokenStore);
        //access_token的过期时间
        services.setAccessTokenValiditySeconds(60 * 60 * 2);
        //refresh_token的过期时间
        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);

        //设置令牌增强,使用JwtAccessTokenConverter进行转换
        services.setTokenEnhancer(jwtAccessTokenConverter);
        return services;
    }

    /**
     * todo 暂时设置授权码模式内存模式
     * @return
     */
/*
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {

        return new InMemoryAuthorizationCodeServices();
    }
*/

    /**
     * 授权码模式保存到数据库
     * @param dataSource
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {

        return new JdbcAuthorizationCodeServices(dataSource);
    }

    @Bean
    public ClientDetailsService clientDetailsService(DataSource dataSource) {

        ClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService) jdbcClientDetailsService).setPasswordEncoder(passwordEncoder);

        return jdbcClientDetailsService;
    }
}

3、配置SpringSecurity管理器
package team.lcf.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import team.lcf.filter.LoginFilter;
import team.lcf.pub.RespBean;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @program: LuoThinking-cloud
 * @description: Security安全配置
 * @author: Cheng Zhi
 * @create: 2022-05-25 10:11
 **/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

/*    @Bean
    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }*/

    /**
     * 配置加密规则
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){

        return new BCryptPasswordEncoder();
    }

    /**
     * 配置显示用户找不到异常,如果不配置默认为true,为ture表示隐藏错误信息,展示BadCredentialsException异常
     * @return
     */
    @Bean
    public Boolean hideUserNotFoundExceptions() {
        return false;
    }

/*    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // TODO 这里暂时写死,之后转移到数据库
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(new BCryptPasswordEncoder().encode("123"))
                .roles("admin")
                .and()
                .withUser("user")
                .password(new BCryptPasswordEncoder().encode("123"))
                .roles("user");
        super.configure(auth);
    }*/

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // TODO 这里之后要重新配置,参照FrameUser

        http.antMatcher("/oauth/**")
                .authorizeRequests()
                .antMatchers("/oauth/**")
                .permitAll()
                .and().csrf().disable();

    }
}

4、配置用户信息查询
package team.lcf.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import team.lcf.enums.ErrorCode;
import team.lcf.exception.GeneralException;
import team.lcf.mapper.FrameUserMapper;
import team.lcf.model.FrameUser;
import team.lcf.model.Role;
import team.lcf.pub.BeanHelper;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

/**
 * @program: PostGirl-panent
 * @description: loadUserByUserNameServiceImpl
 * @author: Cheng Zhi
 * @create: 2021-03-30 12:49
 **/
@Service
public class UserServiceImpl implements UserDetailsService {

    @Autowired
    private BeanHelper beanHelper;

    /**
     * 根据用户名查询用户信息,用于登录
     * @param userName
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

        FrameUser user = beanHelper.getBean(FrameUserMapper.class).loadUserByUsername(userName);
        if (user == null) {
            throw new GeneralException(ErrorCode.FILE_ANALYSE_ERROR);
        }
        Integer userId = user.getUserId();
        List<Role> roles = beanHelper.getBean(FrameUserMapper.class).selectRoleByUserId(userId);
        user.setRoles(roles);


        return (UserDetails) user;
    }


}

效果

在这里插入图片描述

注意:

认证服务如果选择数据库模式,需要配置固定的几张表
官网地址: https://github.com/spring-projects/spring-security-oauth/blob/main/spring-security-oauth2/src/test/resources/schema.sql

我这里记录一下我自己的sql:

CREATE TABLE oauth_client_details(
    client_id VARCHAR(128) NOT NULL   COMMENT '客户端ID' ,
    resource_ids VARCHAR(128)    COMMENT '用户ID 客户端额能访问的ID资源集合,多个使用逗号隔开' ,
    client_secret VARCHAR(128)    COMMENT '客户端密码 客户端的访问密钥' ,
    scope VARCHAR(128)    COMMENT '范围 客户端的可选权限范围,read,write,trust,多个权限范围使用逗号隔开' ,
    authorized_grant_types VARCHAR(128)    COMMENT '授权类型' ,
    web_server_redirect_uri VARCHAR(128)    COMMENT '重定向url' ,
    authorities VARCHAR(128)    COMMENT '当前' ,
    access_token_validity BIGINT    COMMENT 'token令牌有效性' ,
    refresh_token_validity BIGINT    COMMENT 'token刷新' ,
    additional_information VARCHAR(512)    COMMENT '附加信息' ,
    autoapprove VARCHAR(32)    COMMENT '自动续约' ,
    ext1 VARCHAR(32)    COMMENT 'ext1' ,
    PRIMARY KEY (client_id)
) COMMENT = '客户端明细信息 保存 Oauth2客户端账号密码、授权、回调地址等';
insert into `oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`, `ext1`) values('password','rid','$2a$10$r9gQOnCC77KBiHUGY0RZh.39ZPJs3lfgF6bMY7wksXzywWO6rX44O','ALL','authorization_code,implicit,client_credentials,password,refresh_token',NULL,NULL,NULL,NULL,NULL,NULL,NULL);

CREATE TABLE oauth_code(
    code INT    COMMENT '验证码' ,
    authentication VARCHAR(128)    COMMENT '身份验证' ,
    ext1 VARCHAR(32)    COMMENT 'ext1' 
) COMMENT = '授权码 存储授权码';

版权声明:本文为cz1803472613原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。