springboot+spring security oauth2实现密码模式,客户端模式,验证码模式

一.OAuth 2 授权模式

OAuth 协议的授权模式共分为 4 种,分别说明如下:
授权码模式:授权码模式(authorization code)是功能最完整、流程最严谨的授权模式。它的特点就是通过客户端的服务器与授权服务器进行交互,国内常见的第三方平台登录功能基本 都是使用这种模式。
简化模式:简化模式不需要客户端服务器参与,直接在浏览器中向授权服务器中请令牌,一般若网站是纯静态页面,则可以采用这种方式。
密码模式:密码模式是用户把用户名密码直接告诉客户端,客户端使用这些信息向授权服务器中请令牌。这需要用户对客户端高度信任,例如客户端应用和服务提供商是同一家公司。
客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权。严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模式还是非常方便的。

由于我主要制作的项目都是基于web的而且是自己使用所以目前只搭建了:密码模式,客户端模式,验证码模式(自己定义的)

二.核心代码

以下代码省略UserDetails和UserDetailsService相关实现

1.主要依赖
 <!-- spring安全组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- oauth2.0支持组件 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
             <version>2.3.3.RELEASE</version>
        </dependency>
2.客户端sql
create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);

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`) VALUES ('client_1', 'order', '$2a$10$n9a19dUkOx/UzGWPJ5PWHukPuNxNd2JfwN9ibi7yfwUIKQXBwuVca', 'select', 'client_credentials,refresh_token', NULL, 'oauth2', NULL, NULL, NULL, NULL);
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`) VALUES ('client_2', 'order', '$2a$10$n9a19dUkOx/UzGWPJ5PWHukPuNxNd2JfwN9ibi7yfwUIKQXBwuVca', 'select', 'password,refresh_token,captcha', NULL, 'oauth2', NULL, NULL, NULL, NULL);

3.授权服务器
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private static final String RESOURCE_IDS = "order";
    @Autowired
    DataSource dataSource;
    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    @Autowired
    UserDetailsService userDetailsService;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    PasswordEncoder passwordEncoder;

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

//        String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode("123456");
//        System.out.println(finalSecret);
//        //配置两个客户端,一个用于password认证一个用于client认证
//        clients.inMemory()
//
//                //client模式
//                .withClient("client_1")
//                .resourceIds(RESOURCE_IDS)
//                .authorizedGrantTypes("client_credentials", "refresh_token")
//                .scopes("select")
//                .authorities("oauth2")
//                .secret(finalSecret)
//
//                .and()
//
//                //密码模式
//                .withClient("client_2")
//                .resourceIds(RESOURCE_IDS)
//                .authorizedGrantTypes("password", "refresh_token")
//                .scopes("select")
//                .authorities("oauth2")
//                .secret(finalSecret);
        //这个地方指的是从jdbc查出数据来存储
        clients.withClientDetails(clientDetails());
    }

    /**
     * 从数据库读取客户端配置
     */
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }


    /**
     * 认证服务端点配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        //获取grant_type类型组合
        TokenGranter tokenGranter = TokenGranterExt.getTokenGranter(authenticationManager, endpoints,  redisTemplate);

        endpoints
                //设置grant_type类型集合
                .tokenGranter(tokenGranter)
                //用户管理
                .userDetailsService(userDetailsService)
                //token存到redis
                .tokenStore(tokenStore())
                //启用oauth2管理
                .authenticationManager(authenticationManager)
                //接收GET和POST
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {

        oauthServer
                //允许表单认证
                .allowFormAuthenticationForClients()
                //.passwordEncoder(passwordEncoder)
                //允许 check_token 访问
                .checkTokenAccess("permitAll()");

    }

    /**
     * 令牌设置,并存入redis
     */
    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

}
4.资源服务器
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private static final String RESOURCE_IDS = "order";
    @Autowired
    private CustomAuthExceptionHandler customAuthExceptionHandler;
    @Autowired
    UserLogoutSuccessHandler userLogoutSuccessHandler;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_IDS)
                //此处是关键,默认stateless=true,只支持access_token形式,
                // OAuth2客户端连接需要使用session,所以需要设置成false以支持session授权
                .stateless(false)
                .accessDeniedHandler(customAuthExceptionHandler)
                .authenticationEntryPoint(customAuthExceptionHandler);
    }

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        //解决springSecurty使用X-Frame-Options防止网页被Frame
        httpSecurity.headers().frameOptions().disable()
                .and()
                .logout()
                .logoutSuccessHandler(userLogoutSuccessHandler);

        httpSecurity
                //.addFilterBefore(loginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                //需要的时候创建session,支持从session中获取认证信息,ResourceServerConfiguration中
                //session创建策略是stateless不使用,这里其覆盖配置可创建session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .requestMatchers().anyRequest()
                .and()
                .anonymous()
                .and()
                .authorizeRequests()
                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()//将PreflightRequest不做拦截。
                .and()
                .authorizeRequests()
                .antMatchers(
                        "/webjars/**",
                        "/swagger/**",
                        "/v2/api-docs",
                        "/doc.html",
                        "/swagger-ui.html",
                        "/swagger-resources/**",
                        "/druid/**",
                        "/open/**").permitAll()
                .and()
                .authorizeRequests()
                .antMatchers("/**").authenticated();//配置所有访问控制,必须认证过后才可以访问
    }
}

5.配置spring security
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 注入AuthenticationManager接口,启用OAuth2密码模式
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        AuthenticationManager manager = super.authenticationManagerBean();
        return manager;
    }

    /**
     * 通过HttpSecurity实现Security的自定义过滤配置
     *
     * @param httpSecurity
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .requestMatchers().anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").permitAll();
    }
}
6.跨域处理
@Configuration
public class GlobalCorsConfiguration {

    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }
}
7.自定义验证码模式
public class CaptchaTokenGranter extends AbstractTokenGranter {


    private static   final String  GRANT_TYPE="captcha";
    private final AuthenticationManager authenticationManager;
    private RedisTemplate redisTemplate;
    public CaptchaTokenGranter(AuthenticationManager authenticationManager,
                               RedisTemplate redisTemplate,
                               AuthorizationServerTokenServices tokenServices,
                               ClientDetailsService clientDetailsService,
                               OAuth2RequestFactory requestFactory) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.redisTemplate=redisTemplate;
    }
    protected CaptchaTokenGranter(AuthenticationManager authenticationManager,
                                  AuthorizationServerTokenServices tokenServices,
                                  ClientDetailsService clientDetailsService,
                                  OAuth2RequestFactory requestFactory,
                                  String grantType) {
        //调用父类 接管GRANT_TYPE类型
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager=authenticationManager;

    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
        String username = parameters.get("username");
        String password = parameters.get("password");
        String captcha=parameters.get("captcha");
        String sessionUUID=parameters.get("sessionUUID");
        System.out.println(captcha+"  "+sessionUUID);
        if (StringUtils.isEmpty(captcha)){
            throw  new InvalidGrantException("验证码不能为空!");
        }
        //正式环境放行

        Object o = redisTemplate.opsForValue().get(sessionUUID);
        if (o==null){
            throw  new  InvalidGrantException("验证码不存在!");
        }
        if (!o.toString().equals(captcha)){
            throw  new  InvalidGrantException("验证码错误!");
        }
        // Protect from downstream leaks of password
        parameters.remove("password");

        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            userAuth = authenticationManager.authenticate(userAuth);
        }
        catch (AccountStatusException ase) {
            //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
            throw new InvalidGrantException(ase.getMessage());
        }
        catch (BadCredentialsException e) {
            // If the username/password are wrong the spec says we should send 400/invalid grant
            throw new InvalidGrantException(e.getMessage());
        }
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate user: " + username);
        }

        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);


    }
}
/**
 * TokenGranter 扩展 将自定义的grant_type类型添加到oauth2中
 * 使用方法:
 * 在configure(AuthorizationServerEndpointsConfigurer endpoints)中:
 * //获取自定义tokenGranter
 *      TokenGranter tokenGranter = TokenGranterExt.getTokenGranter(authenticationManager, endpoints, baseRedis, userClient, socialProperties);
 *      endpoints.tokenGranter(tokenGranter);
 *
 */
public class TokenGranterExt {

    public static TokenGranter getTokenGranter(final AuthenticationManager authenticationManager,
                                               final AuthorizationServerEndpointsConfigurer endpointsConfigurer,
                                               RedisTemplate redisTemplate
    ) {

        //  默认tokenGranter集合 security 自带的
        List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpointsConfigurer.getTokenGranter()));
        //添加验证码
        granters.add(new CaptchaTokenGranter(authenticationManager,  redisTemplate, endpointsConfigurer.getTokenServices(), endpointsConfigurer.getClientDetailsService(), endpointsConfigurer.getOAuth2RequestFactory()));
        return new CompositeTokenGranter(granters);
    }

}

三.测试请求

密码模式
在这里插入图片描述
客户端模式
在这里插入图片描述
重写的验证码模式
在这里插入图片描述
通过token获取数据
在这里插入图片描述
刷新token
在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值