使用SpringSecurity Oauth2.0实现自定义鉴权中心

Oauth2.0是什么不在赘述,本文主要介绍如何使用SpringSecurity Oauth2.0实现自定义的用户校验

1.鉴权中心服务

首先,列举一下我们需要用到的依赖,本文采用的是数据库保存用户信息redis保存token的方式。

pom依赖:

       ---- security依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.3.4.RELEASE</version> </dependency>
---- oauth2.0依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.2.4.RELEASE</version> </dependency>
----- redis依赖用于存储token
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.3.4.RELEASE</version> </dependency>
----- mybatisPlus用于操作数据库
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency>

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

添加完引用后,我们需要配置Oauth鉴权中的两大服务:鉴权服务和资源服务

一、鉴权服务

鉴权服务中我们需要配置存储token用的redis,用于从DB中拉取Client信息的服务类,用于进行Token生产和维护的服务类,最后还要外露获取token的节点和进行token校验的节点

在开始以前首先我们要搞清SpringSecurity中的一些基本类的作用和概念,这里贴出一篇不错的文章: https://www.cnkirito.moe/spring-security-1/

 

1.首先我们要定义我们的用户实体类,必须要实现UserDetails接口,我们的实体类中仅仅简单的定义了用户名和密码,剩下的全部实现自UserDetails接口

public class User implements UserDetails {

    @TableId(type = IdType.AUTO)
    private int id;

    private String username;

    private String password;

    private boolean isEnabled;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setEnabled(boolean enabled) {
        isEnabled = enabled;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return this.isEnabled;
    }
}

数据库脚本:

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(50) NOT NULL,
  `password` varchar(255) NOT NULL,
  `is_account_non_expired` tinyint(4) NOT NULL,
  `is_account_non_locked` tinyint(4) DEFAULT NULL,
  `is_credentials_non_expired` tinyint(4) NOT NULL,
  `is_enabled` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;

 

2.自定义自己的用户查找服务类,必须集成UserDetaailsService接口且实现loadUserByUsername接口,届时spring框架会通过这个方法内的自定义逻辑找到你想要的用户

public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user= userMapper.queryUserByUsername(username);
        if (user==null){
            throw new UsernameNotFoundException("user no found");
        }
        return  user;
    }
}

userMapper:就是一个非常基础的数据库查询语句

<mapper namespace="com.example.oauth2.mapper.UserMapper">
    <select id="queryUserByUsername" resultType="com.example.oauth2.entity.User">
        select * from t_user where user_name=#{username}
    </select>
</mapper>

3.配置鉴权服务器,我们需要把redis和上面自定义的用户查询配置到鉴权服务里

Configuration
//开启鉴权服务器
@EnableAuthorizationServer
public class AuthoriztionServerConfiguration extends AuthorizationServerConfigurerAdapter {

    //全局SpringSecurity AuthenticationManager 需要在SpringSecurity中注入,下文会写到
    @Autowired
    private AuthenticationManager authenticationManager;

    //redis工厂会自动配置
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    //数据源会自动配置
    @Autowired
    private DataSource dataSource;

    //我们自定义的UserDetailsService实现类
    @Autowired
    private UserDetailsService userDetailsService;

    //全局加密编码类
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    /**
     * 用来配置令牌端点的安全约束
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                //允许所有用户访问 /oauth/token节点来获取token
                .tokenKeyAccess("permitAll()")
                //允许已经鉴权通过的用户访问 /oauth/check_token节点
                .checkTokenAccess("isAuthenticated()")
                // 允许表单认证
                .allowFormAuthenticationForClients();
    }

    /**
     * 定义客户端详细信息的配置器
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//         从DB中拉取client信息
        clients.withClientDetails(jdbcClientDetailsService());
    }

    /**
     *配置鉴权服务终结点配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
        endpoints.authenticationManager(this.authenticationManager)
                //配置允许访问的http方式
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
                //用于配置自定义的用户校验服务
                .userDetailsService(userDetailsService)
                //配置自定义的token保存地址
                .tokenStore(tokenStore())
                //用于配置自定义的token维护服务
                .tokenServices(tokenServices());
    }

    /**
     * 配置使用redis来存储token
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }

    /**
     * 配置通过数据的方式去读取client信息
     * @return
     */
    @Bean
    public ClientDetailsService jdbcClientDetailsService(){
        JdbcClientDetailsService jdbcClientDetailsService=new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return  jdbcClientDetailsService;
    }

    /**
     * 自定义token的生产和过期机制
     * @return
     */
    @Bean
    public DefaultTokenServices tokenServices(){
        DefaultTokenServices defaultTokenServices=new DefaultTokenServices();
        defaultTokenServices.setAccessTokenValiditySeconds((int)TimeUnit.HOURS.toSeconds(4));
        defaultTokenServices.setRefreshTokenValiditySeconds((int)TimeUnit.HOURS.toSeconds(2));
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setTokenStore(tokenStore());
        return  defaultTokenServices;
    }

    /**
     * 指定全局加密方式
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

同时我们需要在数据库中建一张client的信息表SQL脚本:

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(256) NOT NULL,
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `authorized_grant_types` varchar(256) DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

完成以上步骤后鉴权服务器就配置完成了,记下来我们需要配置资源服务器

2.资源服务器

资源服务器配置相对简单,我们只需要暴露出可供用户使用的节点即可

@Configuration
//开启资源服务
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception{
        http
                .authorizeRequests()
                //暴露出/oauth/**的所有接口
                .antMatchers("/oauth/**")
                .permitAll()
                .anyRequest()
                .authenticated();
    }
}

配置完资源服务之后最关键的一步来了,我们需要自定义自己的用户校验

3.自定义用户校验

SpringSecurity中所有的校验都是通过AuthenticationProvider来实现的,所以我们需要实现自己的Provider注入到用于校验的Provider列表中去

@Component
public class MyAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    //前面我们自己实现的读取用户信息的实现类
    @Autowired
    private UserDetailsService userDetailsService;

    //全局加密方式
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    //对表单传入的用户信息和线程上下文中的用户进行比对判断是否是正确的用户
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
       //数据库中的密码
        String authPassword = userDetails.getPassword();
        //上下文中的密码
        String tokenPassword = (String) usernamePasswordAuthenticationToken.getCredentials();
        boolean isPass = passwordEncoder.matches(tokenPassword, authPassword);
        if (isPass) {
            return;
        }
        throw new AuthenticationServiceException("password.wrong");
    }

    //根据表单中传入的用户名从数据库中获取用户信息
    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
        UserDetails user = userDetailsService.loadUserByUsername(username);
        if (user == null) {
            throw new AuthenticationServiceException("未找到用户");
        }
        return user;
    }
}

把我们自定的provider注入进去

@Configuration
public class GlobalAuthenticationConfig extends GlobalAuthenticationConfigurerAdapter {

    @Autowired
    private MyAuthenticationProvider authenticationProvider;

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider( authenticationProvider);

    }
}

最后我们需要配置一下SpringSecurity

4.配置SpringSecurity

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAuthenticationProvider authenticationProvider;

    /**
     * 注入用户信息服务
     * @return 用户信息服务对象
     */
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }

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


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

然后配置文件中配好数据库和redis信息

server:
  port: 5000
spring:
  datasource:
    url: xxx
    username: xxx
    password: xxx
    driver-class-name: com.mysql.jdbc.Driver
  redis:
    host: xxx
    port: 6379
    database: 7
    password: xxx


mybatis-plus:
  mapper-locations: classpath:/mapper/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    ROOT: debug

在启动之前我们需要先在数据库中添加一个client和一个用户

@SpringBootTest
class Oauth2ApplicationTests {

    @Autowired
    JdbcClientDetailsService jdbcClientDetailsService;


    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Test
    void contextLoads() {
        //通过我们在鉴权服务中注入的JdbcClientDetailsService来插入一条Client信息
        BaseClientDetails baseClientDetails = new BaseClientDetails();
        baseClientDetails.setClientId("client");
        baseClientDetails.setClientSecret(passwordEncoder.encode("secret"));
        List<String> grantTypes = new ArrayList<>();
        grantTypes.add("password");
        baseClientDetails.setAuthorizedGrantTypes(grantTypes);
        jdbcClientDetailsService.addClientDetails(baseClientDetails);
        //用指定的密码生成器搞一个密码一会手动填到库里这里就不写sql 了
        String str = passwordEncoder.encode("666");
        System.out.println("终于他妈的完事了");/**/
    }

}

注意给scope填个select上面忘记写了

 

 

启动项目访问获取通通token的接口,完美。

 

posted @ 2020-10-10 17:17  Tassdar  阅读(3070)  评论(0编辑  收藏  举报