zoukankan      html  css  js  c++  java
  • 使用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的接口,完美。

  • 相关阅读:
    动态规划----背包问题
    动态规划----最长公共子序列
    贪心算法---codeforce680D Bear and Tower of Cubes
    贪心算法----Fence Repair(POJ 3253)
    线段树拓展----HDU 6315 Naive Operations
    Saruman`s Army(POJ 3069)
    字典序最小问题 Best Cow Line (POJ 3617)
    RMQ
    牛客2018年第二次acm暑期培训----H-Diff-prime Pairs
    牛客2018年第二次多校培训----A-run
  • 原文地址:https://www.cnblogs.com/Tassdar/p/13793892.html
Copyright © 2011-2022 走看看