OAuth2.0四种授权模式及实战

news2024/11/17 0:21:12

OAuth2.0四种授权模式以及Oauth2.0实战

首先我们得了解什么是Oauth2.0,简单来说Oauth2.0它是一个授权协议。我们可能会听说过,使用Oauth2.0来实现单点登录SSO,以及第三方登录。那个什么是授权?

举个通俗易懂的例子,就是第三方人员A要想进入B公司的大厦进行业务交流的时候,因为A并不是B公司的员工,出于安全的缘故,所以他不能够自由的出入B公司的大厦。那个A到了B公司前台的时候,A得去前台和B公司前台工作人员说明来意,并且出示邀请(访问)证明,此时B公司前台工作人员就会给你一张临时工牌让你进入大厦。

在这个例子当中,A没有工牌所以是无法进入B公司大厦里进行业务交流,B公司前台给A一张临时工牌,这个操作就相当于授权。

总的来说,OAuth 2.0 这种授权协议,就是保证第三方(软件)只有在获得授权之后,才可以进一步访问授权者的数据。

1、Oauth2.0授权许可机制协议

Oauth2.0具有多种授权许可机制协议:授权码许可机制、客户端凭据机制、资源拥有者凭据机制(密码模式)和隐式许可机制。

在了解授权许可机制协议之前,我们得需要了解在OAuth 2.0 的体系里面有 4 种角色,按照官方的称呼它们分别是资源拥有者、客户端、授权服务和受保护资源。

  • 资源拥有者(可以指拥有资源的用户)
  • 客户端(可以理解为第三方系统/软件)
  • 授权服务(权限校验和授权系统(认证服务中心))
  • 受保护资源(用户在系统上所具有的资源/或者能够访问的资源)

1.1、授权码许可机制

授权码许可机制的参与者:资源拥有者、客户端、授权服务、受保护资源

授权码模式这种场景下的授权,第三方软件可以通过拿到资源拥有者授权后的授权码,以及注册时的 client_id 和 client_secret 来换回访问令牌 token 的值。

时序图:
在这里插入图片描述

按照上述时序图举个简单的例子,小明使用微信授权方式登录app。

  1. 小明点开手机里面的app,他不想手动输入账号密码登录,而是采用了微信登录。
  2. 点击微信登录按钮,app拉起授权页面。
  3. 微信授权服务器则生成授权页面,用户看见授权页面点击确定按钮进行授权。
  4. 微信授权服务器校验用户身份合法性后生成请求code,点击确认授权后,页面跳转至app页面并携带请求code(授权码)。
  5. app拿到授权码后,携带授权码向授权服务器获取访问令牌access_token。
  6. 拿到access_token后,则携带access_token向受保护资源发起访问。
  7. 校验access_token无误后,受保护资源返回资源数据(个人的身份数据,昵称,地区等信息)。
  8. 成功登录app,小明继续使用app内的功能。

1.1.1、为什么需要生成授权码以及根据授权码获取access_token步骤?

假设从时序图中抹除授权码的流程,那么从第三步,用户点击确定授权,此时资源拥有者与授权服务器就建立起关联,此时,资源拥有者则与第三方软件前端断开关联,界面则会停留在授权界面。然后授权服务器直接把access_token送给第三方软件后端,后端在携带access_token去访问受保护资源。虽然说资源数据已经拿到了,但是如何通知用户呢?因此,得需要建立起用户与第三方软件前端的关联,所以授权服务器生成授权码后重定向到第三方软件前端则是重新建立起用户与第三方软件前端的关联。

既然如此,那么为什么授权服务器不直接重定向传回access_token,首先并不能保证重定向采用的形式是否是https,而且并不是所有的客户端都支持https,所以重定向传回access_token就会增加access_token失窃的风险。虽然access_token需要与client_id,client_secret一起才能够通过授权服务器校验访问到保护资源,但是在安全层面来说,这都是不适合的。在此层面上看,授权码的作用在于access_token不经过用户浏览器, 保护了access_token。

1.1.2、授权码code可以暴露?

1、授权码Authentication code只能用一次,而且会很快超时失效, 使得被截获后难以运用。

2、授权码需要和client id/client secret共同完成认证,才能够获得access_token。就算授权码如果失窃,单凭授权码是无法得到access_token的。

1.1.3、access_token不能暴露在浏览器那么该存放在哪?

重定向传回access_token会使安全保密性要求极高的访问令牌暴露在浏览器,增加访问令牌失窃风险。

刚开始接触Oauth2.0的我也是比较迷,既然access_token不能暴露在浏览器,那么我到底将access_token存放在哪呢?那我前端有如何进行访问那些受保护资源呢?

在我看来,重定向携带的参数在URL上,http协议下重定向传回access_token的形式,是没有经过数据加密的,他会增加令牌失窃的风险。那么关于access_token存放在哪的问题,个人认为通过授权码以及客户端id和secret共同校验后获取的access_token,可以把access_token存放在localStorage中,localStorage虽然是永久存储,但是access_token会有一个有效期,有效期到了之后,即便access_token一直都存在但是有效期过后就无法访问到受保护资源。

1.1.4、sessionStorage和localStorage区别

1、sessionStorage(会话存储)

  • 生命周期:浏览器打开到关闭的过程

  • 大小:5M

  • 保存的位置:浏览器端

// 存储数据
sessionStorage.setItem("name", "nameValue");
// 获取数据
sessionStorage.getItem("name");
// 删除数据
sessionStorage.removeItem("name");
// 删除所有数据
sessionStorage.clear();

2、localStorage(本地存储【永久存储】)

  • 生命周期: 永久,只能人为删除

  • 大小: 5M甚至更大

  • 保存的位置: 浏览器端

// 存储数据
localStorage.setItem("name", "nameValue");
// 获取数据
localStorage.getItem("name");
// 删除数据
localStorage.removeItem("name");

**注意: **不同浏览器无法共享localStorage或sessionStorage中的信息。相同浏览器的不同页面间【相同域名和端口】可以共享相同的 localStorage,但是不同页面或标签页间无法共享sessionStorage的信息。

1.2、资源拥有者凭据机制(密码模式)

客户端凭据机制的参与者:资源拥有者、客户端、授权服务、受保护资源

资源拥有者凭据,顾名思义就是资源拥有者的凭据(账号,密码)。在这场景里面就不存在第三方软件这概念,相当于就是访问系统中的一个子系统,他们之间互相信任。举个例子来说就是,腾讯有许多的游戏,你只需要用qq账号密码就可以登录游戏玩,不需要进行腾讯授权。因为该游戏是腾讯旗下的,他们相互信任的,所以不存在第三方的说法。

时序图:
在这里插入图片描述

1.3、客户端凭据机制

客户端凭据机制的参与者:客户端、授权服务、受保护资源

相当于就是第三方软件访问不需要资源拥有者授权的资源和数据,换句话说在这里客户端也可以看作是资源拥有者。举个例子来说就是第三方软件访问一些公共的服务,譬如说一些地图信息,logo图标等。

这种场景下的授权,便是客户端凭据许可,第三方软件可以直接使用注册时的 client_id 和 client_secret 来换回访问令牌 token 的值。

时序图:
在这里插入图片描述

1.4、隐式许可机制

隐式许可机制的场景适用于没有后端服务的应用,举个例子来说的话就是在浏览器中执行,譬如说JavaScript应用。

在这种情况下,第三方软件对于浏览器就没有任何保密的数据可以隐藏了,也不再需要应用密钥 app_secret 的值了,也不用再通过授权码 code 来换取访问令牌 access_token 的值了。因此,隐式许可授权流程的安全性会降低很多。

这种场景下的授权,第三方软件可以直接使用注册时的 client_id来换回访问令牌 token 的值。

时序图:
在这里插入图片描述

2、Oauth2.0实战

2.1、搭建授权服务器

2.1.1、 AuthorizationServerConfig(授权服务器配置)

完成以下三个配置:

  • ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService)【客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。】
  • AuthorizationServerEndpointsConfigurer:用来配置令牌(token)的访问端点和令牌服务(tokenservices)
  • AuthorizationServerSecurityConfigurer:用来配置令牌端点的安全约束.

配置客户端详情:

/**
     * 用来配置客户端详情服务(ClientDetailsService)
     * 允许客户端自己申请ClientID
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }
    

AuthorizationServerSecurityConfigurer:用来配置令牌端点的安全约束.

  /**
     * 允许ClientSecret明文方式保存并且可以通过表单提交
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("permitAll()").allowFormAuthenticationForClients().passwordEncoder(NoOpPasswordEncoder.getInstance());
    }

AuthorizationServerEndpointsConfigurer:用来配置令牌(token)的访问端点和令牌服务(tokenservices)

  /**
     * AuthorizationServerEndpointsConfigurer:用来配置令牌(token)的访问端点和令牌服务(tokenservices)
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        //添加自定义 token增强
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtTokenEnhancer()));
        endpoints.approvalStore(approvalStore())
                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager);
    }

    /**
     * 自定义的Token增强器,把更多信息放入Token中
     *
     * @return
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new EnhanceTokenEnhancer();
    }

    /**
     * 配置JWT令牌使用非对称加密方式来验证
     *
     * @return
     */
    @Bean
    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        //设置jwt的转换器
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();

        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
                //设置加密的加载文件
                new ClassPathResource(jksProperties.getName()),
                //设置读取秘钥库文件的密码
                jksProperties.getStorePassword().toCharArray());

        //设置获取秘钥的密码
//        KeyPair keyPair = keyStoreKeyFactory.getKeyPair(jksProperties.getAlias());
        //设置获取秘钥的密码
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair("jwt");
        //设置秘钥对象
        converter.setKeyPair(keyPair);
        return converter;
    }

    /**
     * 使用JDBC数据库方式来保存用户的授权批准记录
     *
     * @return
     */
    @Bean
    public JdbcApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    /**
     * 使用JDBC数据库方式来保存授权码
     *
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     * 使用JWT令牌存储
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtTokenEnhancer());
    }

自定义token增强器:EnhanceTokenEnhancer

public class EnhanceTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();
        if (userAuthentication != null) {
            Object principal = userAuthentication.getPrincipal();
            //把用户标识以userDetails这个Key加入到JWT的额外信息中去
            Map<String, Object> additionalInfo = new HashMap<>();
            additionalInfo.put("userDetails", principal);
            additionalInfo.put("torlesse", "torlesse");
            ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInfo);
        }
        return oAuth2AccessToken;
    }
}

2.1.2、spring security配置

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private TorlesseUserDetailsService torlesseUserDetailsService;

    /**
     * 密码的加密方式
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份验证管理器
     * 通过自定义实现userDetailsService来实现
     * 配置了使用BCryptPasswordEncoder哈希来保存用户的密码(生产环境的用户密码肯定不能是明文保存)
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 注入userDetailsService的实现类并通过passwordEncoder进行加密
        auth.userDetailsService(torlesseUserDetailsService).passwordEncoder(passwordEncoder());
    }

    /**
     * 设置认证管理器 便于我们使用 ,使用默认的认证管理器即可
     *
     * @return
     * @throws Exception
     */
    @Override
    @Bean(value = "authenticationManager")
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 设置拦截器
     * 除了"/login","/oauth/authorize"请求外,设置为任意的请求都需要登录认证
     *
     * @param http
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .antMatchers("/login", "/oauth/authorize", "/oauth/token")
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                .formLogin().loginPage("/login");
    }

    /**
     * 静态资源放行【如果存在静态资源的话】
     *
     * @param webSecurity
     */
    @Override
    public void configure(WebSecurity webSecurity) {
        // 静态资源放行
        webSecurity.ignoring().antMatchers("/dist/**", "/moudle/**", "/plugins/**");
    }
}

2.1.3、四种许可机制测试

启动授权服务器验证四种授权模式的场景。

1、隐式许可机制

首先在浏览器中发送请求

http://localhost:8080/oauth/authorize?response_type=token&client_id=torlesse003&redirect_uri=https://baidu.com

在这里插入图片描述
回车后,需要登陆授权
在这里插入图片描述
点击登陆后
在这里插入图片描述
则可以直接获取到access_token

https://www.baidu.com/#access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidG9ybGVzc2VzZXJ2aWNlIl0sInRvcmxlc3NlIjoidG9ybGVzc2UiLCJ1c2VyX25hbWUiOiJ0b3JsZXNzZSIsInNjb3BlIjpbIlRFU1QiXSwiZXhwIjoxNjQ5NzkwMjYzLCJ1c2VyRGV0YWlscyI6eyJwYXNzd29yZCI6bnVsbCwidXNlcm5hbWUiOiJ0b3JsZXNzZSIsImF1dGhvcml0aWVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn0seyJhdXRob3JpdHkiOiJYWFhfREVMRVRFIn1dLCJhY2NvdW50Tm9uRXhwaXJlZCI6dHJ1ZSwiYWNjb3VudE5vbkxvY2tlZCI6dHJ1ZSwiY3JlZGVudGlhbHNOb25FeHBpcmVkIjp0cnVlLCJlbmFibGVkIjp0cnVlfSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlhYWF9ERUxFVEUiXSwianRpIjoiN2NlMDI0Y2MtNDhmZS00YmY4LWIzNGQtYmY1ZmNkYTEzMmYwIiwiY2xpZW50X2lkIjoidG9ybGVzc2UwMDMifQ.hwS8Lk4CZooDqIkvHy28GaHGP5bv795JuD0KkNFxz4L4vdLqH4XT5CT4PHkJxjhFyOSmEavUroFtP0FPSuWHEfMcM-psZh7YnbhV7qnGjXT9iBNQ5hrLNY56D6WnH7EazD02wjoXx6qxBjW0bzrstNVPRLdiLRzvyO5jFrITpVW6X_znqKwiXwp0a0OLxDWkNH0IdXEdJMGBptqMcnJ__92B5ZkW4wlv4l7lTUF3MkrWuEKKhUwEUtWV42OLBrR1XZ6e3KCOIBrwiDZruey6vOt5QIYV-LHQnPFeEf62YhEww5EfgZqOdJ40hdrQ72wQsn5zKiP5CVIxO5wLx3fpTw&token_type=bearer&expires_in=7199&scope=TEST&torlesse=torlesse&userDetails=org.springframework.security.core.userdetails.User%20%5BUsername=torlesse,%20Password=%5BPROTECTED%5D,%20Enabled=true,%20AccountNonExpired=true,%20credentialsNonExpired=true,%20AccountNonLocked=true,%20Granted%20Authorities=%5BROLE_ADMIN,%20XXX_DELETE%5D%5D&jti=7ce024cc-48fe-4bf8-b34d-bf5fcda132f0
2、客户端凭据机制

使用postman发送请求

http://localhost:8080/oauth/token?grant_type=client_credentials&client_id=torlesse001&client_secret=123456

在这里插入图片描述

3、资源拥有者凭据机制(密码模式)

使用postman发送请求

http://localhost:8080/oauth/token?grant_type=password&client_id=torlesse000&client_secret=123456&username=torlesse&password=123456

在这里插入图片描述

4、授权码许可机制

打开浏览器访问

http://localhost:8080/oauth/authorize?response_type=code&client_id=torlesse002&redirect_uri=https://baidu.com

点击登陆,并授权
在这里插入图片描述
在这里插入图片描述
获取授权码

https://www.baidu.com/?code=SBkZt5

在这里插入图片描述
使用postman测试发送请求获取access_token

http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=torlesse002&client_secret=123456&code=SBkZt5&redirect_uri=https://baidu.com

在这里插入图片描述

2.2、搭建客户端

2.2.1 OAuth2Client客户端配置

OAuthClientConfig客户端配置

@Configuration
@EnableOAuth2Sso
public class OAuthClientConfig {
    /**
     * 定义OAuth2RestTemplate
     * 可从配置文件application.yml读取oauth配置注入OAuth2ProtectedResourceDetails
     * @param oAuth2ClientContext
     * @param details
     * @return
     */
    @Bean
    public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oAuth2ClientContext,
                                                 OAuth2ProtectedResourceDetails details) {
        return new OAuth2RestTemplate(details, oAuth2ClientContext);
    }
}

2.2.2 spring security配置

@Configuration
@Order(200)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * /路径和/login路径允许访问,其它路径需要身份认证后才能访问
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers( "/demo**","/login**")
                .permitAll()
                .antMatchers("/test/hello")
                .anonymous()
                .anyRequest()
                .authenticated();
    }
}

2.2.3 场景模拟DemoController

@RestController
@RequestMapping("/demo")
public class DemoController {
    @Autowired
    OAuth2RestTemplate restTemplate;

    /**
     * 用于单点登录测试
     * @param authentication
     * @return
     */
    @GetMapping("/userInfoPage")
    public ModelAndView securedPage(OAuth2Authentication authentication) {
        return new ModelAndView("userInfoPage").addObject("authentication", authentication);
    }

    /**
     * 访问受保护资源
     * @return
     */
    @GetMapping("/remoteCall")
    public String remoteCall() {
        ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://localhost:8081/user/name", String.class);
        return responseEntity.getBody();
    }
}

2.3、搭建资源服务器

2.3.1 资源服务器配置

@Configuration
@EnableResourceServer//启动资源服务器
@EnableGlobalMethodSecurity(prePostEnabled = true)//启动注解的方式进行权限控制
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    /**
     * 声明了资源服务器的ID是torlesseservice,声明了资源服务器的TokenStore是JWT
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
       resources.resourceId("torlesseservice").tokenStore(tokenStore());
    }

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

    /**
     * 配置公钥
     * @return
     */
    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        Resource resource = new ClassPathResource("public.cert");
        String publicKey = null;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        converter.setVerifierKey(publicKey);
        return converter;
    }


    /**
     * 配置了除了/user路径之外的请求可以匿名访问
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(HttpMethod.POST,"/user/**").authenticated()
                .antMatchers(HttpMethod.GET,"/user/**").authenticated()
                .anyRequest().permitAll();
    }
}

2.3.2 受保护资源UserController

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

    @Autowired
    private TokenStore tokenStore;

    /***
     * 管理员可访问,返回登录用户名
     * @param authentication
     * @return
     */
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/name")
    public String name(OAuth2Authentication authentication) {
        return authentication.getName();
    }

    /**
     * 超级管理员可访问,返回登录用户信息
     *
     * @param authentication
     * @return
     */
    @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')")
    @GetMapping
    public OAuth2Authentication testRoleSuperAdmin(OAuth2Authentication authentication) {
        return authentication;
    }

    /**
     * 只有ROLE_ADMIN权限可以访问,返回访问令牌中的额外信息
     * @param authentication
     * @return
     */
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @PostMapping
    public Object testRoleAdmin(OAuth2Authentication authentication) {
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
        OAuth2AccessToken accessToken = tokenStore.readAccessToken(details.getTokenValue());
        return accessToken.getAdditionalInformation().getOrDefault("userDetails", null);
    }
}

2.4、 模拟测试

2.4.1 postman模拟客户端访问受保护资源

使用torlesse用户拿到token,然后使用postman模拟客户端访问受保护资源
在这里插入图片描述
postman作为模拟客户端,模拟访问受保护资源

http://localhost:8081/user/name

在这里插入图片描述

2.4.2客户端访问受保护资源

浏览器访问:http://localhost:8083/torlesse/demo/remoteCall
在这里插入图片描述
点击登录
在这里插入图片描述
访问到受保护资源演示成功。

2.4.3演示单点登录

准备工作

1)启动两个客户端 端口分别问8082 8083

1.1、修改客户端配置文件application.yml

server:
  port: ${PORT:8083}

1.2、配置多个客户端
在这里插入图片描述
VM options设置:-DPORT=8082
在这里插入图片描述
启动
在这里插入图片描述

单点登录验证

浏览器访问:http://localhost:8083/torlesse/demo/userInfoPage
在这里插入图片描述
点击登录
在这里插入图片描述
浏览器访问:http://localhost:8082/torlesse/demo/userInfoPage
在这里插入图片描述
单点登录演示成功。

2.5、自定义授权模式

Oauth2.0具有多种授权许可机制协议:授权码许可机制、客户端凭据机制、资源拥有者凭据机制(密码模式)和隐式许可机制。

在源码中即可看到四种模式的实现类,还有一个RefreshTokenGranter则是刷新令牌,用于access_token失效时刷新过期时间。
在这里插入图片描述
假如现在我需要实现手机验证码登录或者微信扫码登录等功能的时候,我们该如何处理呢?

我们可以继承AbstractTokenGranter实现自定义授权模式。

手机短信验证码模式如下:

2.5.1 继承AbstractTokenGranter类, 实现手机验证码自定义模式

@Slf4j
public class SmsCodeGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "sms_code";

    protected final AuthenticationManager authenticationManager;

    protected final UserMapper userMapper;

    public SmsCodeGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                          ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, UserMapper userMapper) {
        super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.authenticationManager = authenticationManager;
        this.userMapper = userMapper;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

        Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
        String telephone = parameters.get("telePhone");
        String code = parameters.get("code");

        if (StringUtils.isEmpty(telephone) || StringUtils.isEmpty(code)) {
            throw new InvalidGrantException("参数错误.");
        }
        CheckParam checkParam = new CheckParam();
        checkParam.setTelePhone(telephone);
        User user = userMapper.selectUserByCondition(checkParam.getTelePhone());

        log.info("telephone = {}, code = {}, user = {}", telephone, code, JSON.toJSONString(user));
        // 根据手机号码查询用户信息
        if (user == null) {
            throw new InvalidGrantException("手机号码填写错误.");
        }

        Authentication userAuth = new TelePhoneAuthenticationToken(user.getUsername(), user.getPassword(), telephone, code);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);

        try {
            userAuth = this.authenticationManager.authenticate(userAuth);
        } catch (AccountStatusException var8) {
            throw new InvalidGrantException("当前用户已经被锁定,请联系客服.");
        } catch (BadCredentialsException var9) {
            throw new InvalidGrantException("用户信息查询异常,请确认是否注册.");
        } catch (InternalAuthenticationServiceException var10) {
            throw new InvalidGrantException("验证码校验失败.");
        }

        if (userAuth != null && userAuth.isAuthenticated()) {
            OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
            return new OAuth2Authentication(storedOAuth2Request, userAuth);
        } else {
            throw new InvalidGrantException("Could not authenticate user: " + telephone);
        }

    }
}

手机验证码token

public class TelePhoneAuthenticationToken extends UsernamePasswordAuthenticationToken {

    private String telePhone;

    private String code;

    /**
     * @param principal 用户名
     */
    public TelePhoneAuthenticationToken(Object principal, Object credentials, String telePhone, String code) {
        super(principal, credentials);
        setAuthenticated(false);
        this.telePhone = telePhone;
        this.code = code;
    }

    public String getTelePhone() {
        return telePhone;
    }

    public String getCode() {
        return code;
    }
}

2.5.2 授权服务器配置修改

AuthorizationServerConfig授权服务器配置类中添加TokenGranter

 /**
     * 初始化所有的TokenGranter
     */
    private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {

        ClientDetailsService clientDetails = endpoints.getClientDetailsService();
        AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
        OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();

        List<TokenGranter> tokenGranters = new ArrayList<>();
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
                requestFactory));
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
        ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
        tokenGranters.add(implicit);
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
        if (authenticationManager != null) {
            tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
                    clientDetails, requestFactory));
            tokenGranters.add(new SmsCodeGranter(authenticationManager, endpoints.getTokenServices(),
                    endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), userMapper));
        }
        return tokenGranters;
    }

修改AuthorizationServer配置令牌访问端点,添加以下内容
在这里插入图片描述

 @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer()));
        // 初始化所有的TokenGranter,并且类型为CompositeTokenGranter
        List<TokenGranter> tokenGranters = getDefaultTokenGranters(endpoints);
        endpoints.approvalStore(approvalStore())
                .tokenGranter(new CompositeTokenGranter(tokenGranters))
                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager);
    }

2.5.3 手机号验证码授权模式验证演示

postman模拟客户端发送请求

http://localhost:8080/oauth/token?grant_type=sms_code&client_id=torlesse004&client_secret=123456&telePhone=12345678999&code=123123

在这里插入图片描述
项目访问地址Github:github
项目访问地址Gitee:gitee

书籍推荐

以下是一些关于OAuth2.0的书籍推荐及其理由:

  1. 《OAuth 2实战》:本书深入探讨OAuth的运行机制,详细介绍如何在不安全的网络环境下正确使用、部署OAuth,确保安全认证,是目前关于OAuth最全面深入的参考资料。
  2. 《OAuth 2权威指南》:本书是一本全面而深入地介绍OAuth2.0的书籍。它涵盖了从基础知识到高级主题的所有内容,并提供了大量示例和代码。
  3. 《Mastering OAuth 2.0》:本书从客户的角度提供了OAuth 2.0协议的深入观点。注重实用性和安全性。本书探讨了客户端与OAuth 2.0服务提供者集成的各种方式。在此过程中,讨论一些注意事项和最佳实践。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/689754.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

概率论与数理统计教程第六章节笔记

参考书籍&#xff1a;概率论与数理统计教程第三版 茆诗松 程依明 濮晓龙 编著 文章声明&#xff1a;如有错误还望批评指正 文章目录 ξ \xi ξ 6.1点估计的概念与无偏性 ξ 6.2 \xi6.2 ξ6.2矩估计及相和性 ξ 6.3 \xi6.3 ξ6.3最大似然估计与EM算法 ξ 6.6 \xi6.6 ξ6.6区间估…

Pandas 解决保存excel文件发生异常问题

代码&#xff1a; #保存excel my.to_excel(df.xlsx,#设置Excel1的工作表名sheet_name表1) 异常&#xff1a; 原因&#xff1a;没有导入这个库openpyxl 解决&#xff1a; 1) 使用 pip list 查看 2) 如果没有&#xff0c;则安装 pip install openpyxl 3) 再导入 import openpyx…

uniapp中引入uview教程

uview官网&#xff0c;本次教程中用不到&#xff0c;若需要查看官网教程&#xff0c;可点击前往 1、在插件市场中搜索uview&#xff0c;并导入项目&#xff0c;点击前往 2、如果没有安装scss&#xff0c;需要安装scss依赖&#xff0c;如已安装&#xff0c;请跳过 // 安装sass…

【刷题笔记】反转链表——头插法/栈实现

【刷题笔记】反转链表——头插法/栈实现 解法一&#xff1a;头插法 思路及代码&#xff1a; * 1、创建一个newheadnull&#xff0c;即最终反转后的链表的头结点* 2、循环遍历当前的链表的head&#xff0c;创建temp记录当前head的next&#xff0c;然后将head的next指向新的头ne…

Acer宏碁Swift笔记本电脑SF314-54原装Win10系统工厂模式恢复原厂OEM出厂系统镜像

Acer宏基&#xff0c;Acer宏碁Swift笔记本电脑&#xff0c;Swift SF314-54原装Windows10系统工厂模式恢复原厂OEM出厂状态镜像 系统自带所有驱动、Office办公软件、出厂主题壁纸LOGO、 Acer Care Center、Quick Access等预装程序 所需要工具&#xff1a;32G或以上的U盘&#…

springboot+vue高校科研队伍管理系统_2byeq-

1.登录和注册&#xff1a;创建用户密码后用户输入正确用户密码即可登录&#xff0c;超级管理员可查看平台内所有账号信息。 2.个人信息管理&#xff1a;支持修改个人信息以及保存。 3.科研队伍管理&#xff1a;支持创建科研队伍&#xff0c;个人可以创建队伍后自己成为组长&…

uniapp倒计时

uniapp实现根据传递的时间展示倒计时 需求说明&#xff1a;听书倒计时&#xff0c;设置完时间展示倒计时 countDownTime(showTime){ //showTime为传递的时间 默认在转化成分钟var that this;that.times showTime * 60that.timer setInterval(function() {that.times--;if(th…

教程 | Datavines 自定义数据质量检查规则(Metric)

Metric 是 Datavines 中一个核心概念&#xff0c;一个 Metric 表示一个数据质量检查规则&#xff0c;比如空值检查和表行数检查都是一个规则。Metric 采用插件化设计&#xff0c;用户可以根据自己的需求来实现一个 Metric。下面我们来详细讲解一下如何自定义Metric。 第一步 …

WPF 零基础入门笔记(3):数据绑定详解(更新中)

文章目录 文章合集数据绑定数据绑定实战事件通知型数据驱动&#xff0c;双向绑定资源绑定数据源绑定全局数据源后端和前端绑定问题 文章合集 WPF基础知识博客专栏 WPF微软文档 WPF控件文档 B站对应WPF数据绑定视频教程 数据绑定 我们在之前的文章中&#xff0c;详细解释了数…

windows配置jmeter定时任务

场景&#xff1a; 需要让脚本在指定的执行 步骤&#xff1a; 准备jmeter脚本&#xff0c;保证在命令行中可以调用脚本且脚本运行正常&#xff1a;"C:\Apache\jmeter\bin\jmeter.bat" -n -t C:\tests\test_plan.jmx -l C:\tests\results.jtl -t : 指定执行jmeter脚…

chatgpt赋能python:Python计算CCI指标的介绍

Python计算CCI指标的介绍 CCI&#xff08;Commodity Channel Index&#xff09;是一种技术指标&#xff0c;是推断价格高低位和趋势变化的一种工具。通过计算股票、期货、外汇和其他市场的典型价格、最高价和最低价以及CCI的值&#xff0c;可以预测未来价格趋势并进行交易。 …

Solidity第二次作业

目录 第一题 第二题 第三题 第四题 第五题 第六题 第一题 // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.6.0; contract math { //1.根据所属类型值域&#xff0c;修改变量numa与numb值 uint8 numa 256; int8 numb 128; int numc 255; fun…

安科瑞无线测温系统在高压开关柜中的应用

摘要&#xff1a;高压开关柜是配电系统中重要的组成部分&#xff0c;其主要作用是控制电荷、分配电能和开断电流等&#xff0c;对维持系统的稳定性有一定的保障作用。将无线测温技术应用于高压开关柜&#xff0c;可以实现对其进行实时的动态监测&#xff0c;有助于相关工作人员…

使用CSS的polygon属性画各个方向的半圆环

CSS的polygon属性 CSS polygon()函数是一个图形函数&#xff0c;用于指定某种基本图形类型。polygon()函数用于定义一个多边形 .container{width: 50px;height: 50px;border: 13px solid #0c73fe;border-radius: 50px;/* 上半圆环 */clip-path: polygon(100% 50%, 0 50%, 0 0, …

Redis cluster集群搭建集群增删集群节点

1.Redis cluster集群架构&#xff08;本机ip:192.168.2.100&#xff09; 建立三台linux服务器&#xff0c;分别是192.168.2.61、192.168.2.62、192.168.2.63 1.1.建立192.168.2.61Linux服务器 1.配置Linux服务器 # 在 /etc/sysconfig/network-scripts/ifcfg-eno16777736文件…

C# 读写ABPLC( Allen Bradley)

1.安装 libplctag.net库 GitHub - libplctag/libplctag.NET: This is a .NET wrapper for libplctag. 2.PLC IP和tag 3.写入值 var myTag new TagReal(){//Name is the full path to tag. Name "HMI_F26[0]",//Gateway is the IP Address of the PLC or communicat…

7-WebApis-3

Web APIs - 3 目标&#xff1a;学习事件流&#xff0c;事件委托&#xff0c;其他事件等知识&#xff0c;优化多个事件绑定和实现常见网页交互 事件流移除事件监听其他事件元素尺寸与位置综合案例 事件流 为什么要学习事件流&#xff1f; 可以帮我们解决一些疑惑&#xff0c;比…

npm全局安装的包在cmd能找到,在powershell中找不到

背景 使用npm i g 全局安装的包&#xff0c;比如&#xff1a;eslint&#xff1b;安装完成后&#xff0c;执行eslint相关命令&#xff0c;显示命令不存在&#xff1b;以为是node没配置全局环境变量&#xff0c;检查发现配置了&#xff1b;后来试了一下在cmd是可以使用的&#x…

蓝桥杯专题-试题版-【十进制转十六进制】【十六进制转八进制】【十六进制转十进制】【数的读法】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

TCP客户端的WPF实现

1、TCP实现类 internal class TcpClient{Socket ClientSocket { get; set; }public byte[] ReceiveBuffer { get; set; }/// <summary>/// 构造函数/// </summary>/// <param name"receiveBufferLength"></param>public TcpClient(int rece…