【实战】Spring Security Oauth2自定义授权模式接入手机验证

news2024/11/14 17:27:48

文章目录

    • 前言
    • 技术积累
      • Oauth2简介
      • Oauth2的四种模式
        • 授权码模式
        • 简化模式
        • 密码模式
        • 客户端模式
        • 自定义模式
    • 实战演示
      • 1、mavan依赖引入
      • 2、自定义手机用户
      • 3、自定义手机用户信息获取服务
      • 4、自定义认证令牌
      • 5、自定义授权模式
      • 6、自定义实际认证提供者
      • 7、认证服务配置
      • 8、Oauth2配置
      • 9、资源服务配置
    • 测试用例
      • 1、创建测试请求
      • 2、测试用例预演
      • 3、测试结果
    • 写在最后

前言

最近在修改一个之前使用Oauth2的旧项目,该项目使用了原始的账号密码登录。现在有一个需求是在当前的基础上增加手机验证码登录,让系统可以同时支持账密、手机验证码登录两种方式。哈哈,看到这个需求心里就想着可以参照Oauth2的账密验证逻辑进行改造,比如自定义认证对象、自定义授权模式、自定义实际授权者,最后将上面几个添加到Oauth2里面让其能够识别即可。

在这里插入图片描述

技术积累

Oauth2简介

OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源 (如头像、照片、视频等),并且在这个过程中无须将用户名和密码提供给第三方应用。通过令牌 (token) 可以实现这一功能。每一个令牌授权一个特定的网站在特定的时间段内允许可访问特定的资源。

OAuth 让用户可以授权第三方网站灵活访问它们存储在另外一些资源服务器上的特定信息,而非所有的内容。对于用户而言,我们在互联网应用中最常见的 OAuth 应用就是各种第三方登录,例如 QQ授权登录、微信授权登录、微博授权登录、GitHub 授权登录等。

Oauth2的四种模式

授权码模式

大概就是客户端需要拿到一个预授权码,然后通过预授权码换取真实的token,最后使用token进行访问。比如我们的微信授权登录、飞书授权登录等等。
在这里插入图片描述

简化模式

简化模式是授权码模式简化版本,客户端不用先申请预授权码,直接通过具体的参数拿到token,最后访问资源。比如我们微信appId、appSecrt拿到access_token。
在这里插入图片描述

密码模式

这个就是非常原始的账户、密码登录,客户端携带账户和密码直接拿到token,最后使用token进行资源访问。比如我们现在常规系统的账密登录。
在这里插入图片描述

客户端模式

这种直接客户端向授权服务注册,直接拿到token,比如会员客户端等等。

在这里插入图片描述

自定义模式

自定义模式就是我们今天的重点,我们不用Oauth2的四种模式,直接用自己的方式实现一个手机验证码登录功能。

大概得流程就是:
1、自定义认证令牌
extends AbstractAuthenticationToken
自由实现未认证和已认证的构造令牌的方法

2、自定义授权模式
extends AbstractTokenGranter
在授权模式中我们会获取到认证路径中的手机号和验证码,并产生一个未认证的认证令牌交给Oauth2

3、自定义实际认证提供者
implements AuthenticationProvider
在实际的认证者中我们会拿到在授权模式中提供的手机号和验证码,然后进行业务比对,比如是否存在、是否过期等等。如果全部验证通过会产生一个完成认证的认证令牌交给Oauth2

4、授权配置
extends AuthorizationServerConfigurerAdapter
这个里面需要配置 客户端
ClientDetailsServiceConfigurer
以及将自定义和默认授权模式交给Oauth2 AuthorizationServerEndpointsConfigurer

5、Oauth2配置
extends WebSecurityConfigurerAdapter
配置哪些请求需要进行拦截和免登录
configure(HttpSecurity http)
配置密码模式的用户名和密码,并将自定义认证提供者加入
configure(AuthenticationManagerBuilder auth)

6、资源服务器配置
extends ResourceServerConfigurerAdapter
定义哪些资源需要被拦截和放行
configure(HttpSecurity http)
如果是生成环境需要配置token策略和服务端保持一致。

实战演示

本次演示是将资源服务和客户服务放置在同一个项目上,所有采用的内存报错token。如果使用在生产环境请修改为从数据库拿去用户和客户信息,以及token持久化。

1、mavan依赖引入

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
    <java.version>8</java.version>
    <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!--安全模块-->
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>

2、自定义手机用户

PhoneUser

import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;

/**
 * PhoneUser
 * @author senfel
 * @version 1.0
 * @date 2024/8/7 18:34
 */
public class PhoneUser extends User {
    @Getter
    private Integer id;
    @Getter
    private String name;
    @Getter
    private String phone;
    public PhoneUser(Integer id,String name,String phone,String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
        this.id = id;
        this.name = name;
        this.phone = phone;
    }
}

3、自定义手机用户信息获取服务

PhoneUserDetailsService

import cn.hutool.core.util.ArrayUtil;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.Component;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * PhoneUserDetailsService
 * 手机用户信息获取服务
 * @author senfel
 * @version 1.0
 * @date 2024/8/7 18:28
 */
@Component
public class PhoneUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {
        System.err.println("PhoneUserDetailsService获取到的phone:"+phone);
        //TODO 从数据库获取手机用户数据,默认admin_role权限集
        String[] permissionArr = new String[]{"admin_role"};
        return new PhoneUser(1,"senfel-test-phone","18788888888","18788888888","", initAuthority(permissionArr));
    }

    private Collection<? extends GrantedAuthority> initAuthority(String[] permissionArr) {
        Set<String> dbAuthsSet = new HashSet<>();
        if (ArrayUtil.isNotEmpty(permissionArr)) {
            dbAuthsSet.addAll(Arrays.asList(permissionArr));
        }
        return AuthorityUtils.createAuthorityList(dbAuthsSet.toArray(new String[0]));
    }
}

4、自定义认证令牌

PhoneAuthenticationToken

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;

/**
 * PhoneAuthenticationToken
 * 手机验证码认证令牌
 * @author senfel
 * @version 1.0
 * @date 2024/8/7 17:33
 */
public class PhoneAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 530L;

    //表示认证主体,通常是用户对象(UserDetails),这里传入手机号
    private final Object principal;
    //存储了与主体关联的认证信息,例如密码,这里传入验证码
    private Object credentials;

    /**
     * 自定义已未证对象
     * @param principal
     * @param credentials
     * @author senfel
     * @date 2024/8/7 17:39
     * @return
     */
    public PhoneAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

    /**
     * 自定义已认证对象
     * @param authorities 表示主体所拥有的权限集合
     * @param principal
     * @param credentials
     * @author senfel
     * @date 2024/8/7 17:39
     * @return
     */
    public PhoneAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object principal, Object credentials) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        //是否认证标识
        setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return this.credentials;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }
}

5、自定义授权模式

PhoneCodeTokenGranter

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * PhoneCodeTokenGranter
 * 手机验证码授权模式
 * @author senfel
 * @version 1.0
 * @date 2024/8/7 17:43
 */
public class PhoneCodeTokenGranter extends AbstractTokenGranter {
    //授权类型名称
    private static final String GRANT_TYPE = "phonecode";

    private final AuthenticationManager authenticationManager;


    /**
     * 手机验证码授权模式构造函数
     * @param tokenServices
     * @param clientDetailsService
     * @param requestFactory
     * @param authenticationManager
     * @author senfel
     * @date 2024/8/7 18:06
     * @return
     */
    public PhoneCodeTokenGranter( AuthenticationManager authenticationManager,AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        this(authenticationManager,tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
    }

    public PhoneCodeTokenGranter(AuthenticationManager authenticationManager,AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        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 phone = parameters.get("phone");
        String phonecode = parameters.get("phonecode");
        //创建未认证对象
        Authentication userAuth = new PhoneAuthenticationToken(phone, phonecode);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            //进行身份认证
            userAuth = authenticationManager.authenticate(userAuth);
        } catch (AccountStatusException ase) {
            //将过期、锁定、禁用的异常统一转换
            throw new InvalidGrantException(ase.getMessage());
        } catch (BadCredentialsException e) {
            // 验证码错误,我们应该发送400/invalid grant
            throw new InvalidGrantException(e.getMessage());
        }
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("用户认证失败: " + phone);
        }
        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }

}

6、自定义实际认证提供者

PhoneAuthenticationProvider

import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * PhoneAuthenticationProvider
 * 手机验证码实际认证供应者
 * @author senfel
 * @version 1.0
 * @date 2024/8/7 18:12
 */
@Setter
public class PhoneAuthenticationProvider implements AuthenticationProvider {

    private StringRedisTemplate redisTemplate;

    private PhoneUserDetailsService phoneUserDetailsService;

    public static final String PHONE_CODE_SUFFIX = "phone:code:";

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //先将authentication转为我们自定义的Authentication对象
        PhoneAuthenticationToken authenticationToken = (PhoneAuthenticationToken) authentication;
        //校验参数
        Object principal = authentication.getPrincipal();
        Object credentials = authentication.getCredentials();
        if (principal == null || "".equals(principal.toString()) || credentials == null || "".equals(credentials.toString())){
            throw new InternalAuthenticationServiceException("手机/手机验证码为空!");
        }
        //获取手机号和验证码
        String phone = (String) authenticationToken.getPrincipal();
        String code = (String) authenticationToken.getCredentials();
        //查找手机用户信息,验证用户是否存在
        UserDetails userDetails = phoneUserDetailsService.loadUserByUsername(phone);
        if (userDetails == null){
            throw new InternalAuthenticationServiceException("用户手机不存在!");
        }
        String codeKey =  PHONE_CODE_SUFFIX+phone;
        //手机用户存在,验证手机验证码是否正确
        if (!redisTemplate.hasKey(codeKey)){
            throw new InternalAuthenticationServiceException("验证码不存在或已失效!");
        }
        String realCode = redisTemplate.opsForValue().get(codeKey);
        if (StringUtils.isBlank(realCode) || !realCode.equals(code)){
            throw new InternalAuthenticationServiceException("验证码错误!");
        }
        //返回认证成功的对象
        PhoneAuthenticationToken phoneAuthenticationToken = new PhoneAuthenticationToken(userDetails.getAuthorities(),phone,code);
        //details是一个泛型属性,用于存储关于认证令牌的额外信息。其类型是 Object,所以你可以存储任何类型的数据。这个属性通常用于存储与认证相关的详细信息,比如用户的角色、IP地址、时间戳等。
        phoneAuthenticationToken.setDetails(userDetails);
        return phoneAuthenticationToken;
    }


    /**
     * ProviderManager 选择具体Provider时根据此方法判断
     * 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口
     */
    @Override
    public boolean supports(Class<?> authentication) {
        //isAssignableFrom方法如果比较类和被比较类类型相同,或者是其子类、实现类,返回true
        return PhoneAuthenticationToken.class.isAssignableFrom(authentication);
    }

}

7、认证服务配置

AuthorizationServerConfig

import com.example.ccedemo.security.PhoneCodeTokenGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.ArrayList;
import java.util.List;

/**
 * AuthorizationServerConfig
 * 认证服务配置
 * @author senfel
 * @version 1.0
 * @date 2024/8/7 19:22
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 配置允许访问系统的客户端
     * @param clients
     * @author senfel
     * @date 2024/8/8 14:17
     * @return void
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //clients.jdbc()生产环境从数据库加载
        //模拟内置一个系统客户端
        clients.inMemory().withClient("admin").secret("{bcrypt}"+new BCryptPasswordEncoder().encode("12315")).scopes("server")
                .authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token","phonecode");
    }

    /**
     * 断点配置
     * @param endpoints
     * @author senfel
     * @date 2024/8/8 14:17
     * @return void
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //tokenGranter
        endpoints.tokenGranter(new CompositeTokenGranter(initGranters(endpoints)));
    }

    /**
     * 加入所有自定义的和原有的认证模式配置
     * @param endpoints
     * @author senfel
     * @date 2024/8/9 9:33
     * @return java.util.List<org.springframework.security.oauth2.provider.TokenGranter>
     */
    private List<TokenGranter> initGranters(AuthorizationServerEndpointsConfigurer endpoints) {
        //TODO 如果资源服务与授权服务分开,则需要token持久化,这里默认使用内存存储
        AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
        ClientDetailsService clientDetailsService = endpoints.getClientDetailsService();
        OAuth2RequestFactory oAuth2RequestFactory = endpoints.getOAuth2RequestFactory();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
        //自定义Granter
        List<TokenGranter> customTokenGranters = new ArrayList<>();
        customTokenGranters.add(new PhoneCodeTokenGranter(authenticationManager, tokenServices, clientDetailsService, oAuth2RequestFactory));
        //添加密码模式
        customTokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, oAuth2RequestFactory));
        //刷新模式
        customTokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, oAuth2RequestFactory));
        //简易模式
        customTokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, oAuth2RequestFactory));
        //客户端模式
        customTokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, oAuth2RequestFactory));
        //授权码模式
        customTokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, oAuth2RequestFactory));
        return customTokenGranters;
    }

}

8、Oauth2配置

OAuth2SecurityConfig

import com.example.ccedemo.security.PhoneAuthenticationProvider;
import com.example.ccedemo.security.PhoneUserDetailsService;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * OAuth2SecurityConfig
 * @author senfel
 * @version 1.0
 * @date 2024/8/7 19:08
 */
@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private PhoneUserDetailsService phoneUserDetailsService;

    @Override
    @SneakyThrows
    protected void configure(HttpSecurity http) {
        http.csrf().disable()// 关闭csrf
                .authorizeRequests()
                .anyRequest().authenticated();
    }

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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //auth.jdbcAuthentication()生产环境从数据库加载
        //模拟验证用户名密码登录
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("{bcrypt}"+new BCryptPasswordEncoder().encode("123456"))
                //设置权限
                .authorities("admin_role")
                .and()
                .withUser("user")
                .password("{bcrypt}"+new BCryptPasswordEncoder().encode("123456"))
                //设置权限
                .authorities("user_role");

        //放入自定义的认证提供者
        auth.authenticationProvider(phoneAuthenticationProvider());
    }

   /**
    * 手机验证码登录的认证提供者
    * @author senfel
    * @date 2024/8/9 9:34
    * @return com.example.ccedemo.security.PhoneAuthenticationProvider
    */
    @Bean
    public PhoneAuthenticationProvider phoneAuthenticationProvider(){
        //实例化provider,把需要的属性set进去
        PhoneAuthenticationProvider phoneAuthenticationProvider = new PhoneAuthenticationProvider();
        phoneAuthenticationProvider.setRedisTemplate(redisTemplate);
        phoneAuthenticationProvider.setPhoneUserDetailsService(phoneUserDetailsService);
        return phoneAuthenticationProvider;
    }
}

9、资源服务配置

ResourceServerConfig

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

/**
 * ResourceServerConfig
 * 资源服务配置
 * @author senfel
 * @version 1.0
 * @date 2024/8/9 9:35
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /**
     * 配置security的安全机制
     * TODO 如果资源服务与授权服务分开,则需要token持久化
     * @author senfel
     * @date 2024/8/9 9:56
     * @return void
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //#oauth2.hasScope()校验客户端的权限,这个server是在客户端中的scope
        http.authorizeRequests()
                // 免认证的请求
                .antMatchers("/oauth2-test/get-user-name").permitAll()
                .antMatchers("/**").access("#oauth2.hasScope('server')")
                .anyRequest().authenticated();
    }
}

测试用例

1、创建测试请求

Oauth2Controller

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Oauth2Controller
 * @author senfel
 * @version 1.0
 * @date 2024/8/8 10:43
 */
@RestController
@RequestMapping("/oauth2-test")
public class Oauth2Controller {

    /**
     * 不用鉴权就可以获取用户名称
     * @author senfel
     * @date 2024/8/8 17:53
     * @return java.lang.String
     */
    @RequestMapping("/get-user-name")
    public String getUserName() {
        return "senfel";
    }
    /**
     * 必须admin_role权限才能获取密码
     * @author senfel
     * @date 2024/8/8 17:53
     * @return java.lang.String
     */
    @PreAuthorize("hasAnyAuthority('admin_role')")
    @RequestMapping("/get-user-password")
    public String getUserPassword() {
        return "123321123467";
    }

    /**
     * 有登录权限才能获取年龄
     * @author senfel
     * @date 2024/8/8 17:53
     * @return java.lang.String
     */
    @RequestMapping("/get-user-age")
    public String getUserAge() {
        return "18";
    }
}

2、测试用例预演

我们在上面创建了三个测试请求:
2.1 在资源服务配置中我们将get-user-name路径直接放行,也就是不需要认证也能够访问;
2.2 在Oauth2的配置中我们模拟了两个用户,admin拥有dmin_role权限,user拥有user_role权限。并且我们在get-user-password资源上添加了 @PreAuthorize(“hasAnyAuthority(‘admin_role’)”)仅让有admin_role权限的用户才能访问,那么这里可以让admin用户认证后访问,user登录后访问拒绝;
2.3 在资源服务配置我们让除去name的其他资源都必须认证,则get-user-age资源在所有用户认证成功后都能够访问。

3、测试结果

3.1 不登录进行验证
由于资源配置放行则不登录也可以访问
在这里插入图片描述

3.2 密码模式
3.2.1 调用 /oauth/token登录
使用basic admin 12315客户端验证
admin登录测试

admin登录
在这里插入图片描述
登录后可用访问age资源:
在这里插入图片描述
由于admin有admin_role权限,登录后可用访问password资源:
在这里插入图片描述

user登录测试

user登录
在这里插入图片描述
登录后可用访问age资源:
在这里插入图片描述
由于user只有user_role权限,登录后拒绝访问password资源:

3.3 自定义手机验证登录

手机验证登录
在这里插入图片描述
登录后可访问age资源:
在这里插入图片描述
由于当前phone拥有admin_role权限,登录后可访问password资源:
在这里插入图片描述

写在最后

Oauth2自定义认证模式还是比较简单,直接自定义认证令牌、自定义授权模式、自定义实际认证者、然后将自定义的授权模式和认证者交给Oauth2。最后,我们在资源配置中可以配置受限资源和免登录资源,以及token储存方式、用户加载方式等等即可。

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

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

相关文章

C语言程序设计-[11] 循环结构嵌套

1、循环结构嵌套形式 上面三种循环语句结构可以相互嵌套&#xff0c;组合非常灵活。循环嵌套需要记住最重要的一点&#xff1a;”外循环执行一次&#xff0c;内循环要完整执行一遍”&#xff0c;要通过实例加深对这一句话的理解。 注1&#xff1a;一个循环结构由四个要素构成&…

Java设计模式-建造者模式-一次性理解透

1. 建造者模式简介 今天我们将研究 Java 中的建造者模式&#xff08;Builder 模式&#xff09;。Builder 设计模式是一种创建型设计模式&#xff0c;也被称为生成器模式&#xff0c;类似于工厂模式和抽象工厂模式。 该模式用于创建复杂对象&#xff0c;允许用户创建不同类型的…

【Python】PyWebIO 初体验:用 Python 写网页

目录 前言1 使用方法1.1 安装 Pywebio1.2 输出内容1.3 输入内容 2 示例程序2.1 BMI 计算器2.2 Markdown 编辑器2.3 聊天室2.4 五子棋 前言 前两天正在逛 Github&#xff0c;偶然看到一个很有意思的项目&#xff1a;PyWebIo。 这是一个 Python 第三方库&#xff0c;可以只用 P…

100 Exercises To Learn Rust 挑战!准备篇

公司内部的学习会非常活跃&#xff01;我也参与了Rust学习会&#xff0c;并且一直在研究rustlings。最近&#xff0c;我发现了一个类似于rustlings的新教程网站&#xff1a;Welcome - 100 Exercises To Learn Rust。 rustlings是基于Rust的权威官方文档《The Rust Programming…

汽车免拆诊断案例 | 2010款劳斯莱斯古斯特车中央信息显示屏提示传动系统故障

故障现象  一辆2010款劳斯莱斯古斯特车&#xff0c;搭载N74发动机&#xff0c;累计行驶里程约为11万km。车主反映&#xff0c;起动发动机后组合仪表和中央信息显示屏均提示传动系统故障。用故障检测仪检测&#xff0c;发现发动机控制模块2&#xff08;DME2&#xff09;中存储…

SmartBI拓展包二开入门开发

前言 新接到一个项目拓展包三开的需求&#xff0c;没有相关经验&#xff0c;学习开发&#xff0c;本文尝试通过简单的定位以及指导&#xff0c;确定修改点 SmartBI帮助文档-拓展包开发 登录 http://localhost:18080/smartbi/vision/index.jsp后台配置 上传拓展包&#xff0…

MySQL和Redis的数据一致性

MySQL和Redis的数据一致性 多线程环境下的涉及读写的缓存才会存在MySQL和Redis的数据不一致问题 先删除缓存再更新数据库再延时删除缓存 线程一删除缓存线程一更新数据线程二开始查数据如果第二步线程一更新数据延时&#xff0c;那么线程二会重新从数据库加载数据&#xff0…

超好用的windows系统工具PowerToys

文章目录 Github地址基本介绍使用 Github地址 PowerToys 基本介绍 是windows官方好用的工具箱&#xff0c;包括各种工具 使用 要带上win键 此工具安装后每次运行电脑自启动&#xff0c;桌面没有快捷方式&#xff0c;只能右下角 窗口在上效果演示&#xff0c;会被蓝线框到…

基于GeoTools使用JavaFx进行矢量数据可视化实战

目录 前言 一、JavaFx展示原理说明 二、GeoTools的Maven依赖问题 三、引入Geotools相关的资源包 四、创建JavaFx的Canvas实例 五、JavaFx的Scene和Node的绑定 六、总结 前言 众所周知&#xff0c;JavaFx是Java继Swing之后的又一款用于桌面应用的开发利器。当然&#xff0…

江科大/江协科技 STM32学习笔记P22

文章目录 AD单通道&AD多通道ADC基本结构和ADC有关的库函数AD单通道AD.cmain.c连续转换&#xff0c;非扫描模式的AD.c AD多通道AD.cmain.c AD单通道&AD多通道 ADC基本结构 第一步&#xff0c;开启RCC时钟&#xff0c;包括ADC和GPIO的时钟&#xff0c;ADCCLK的分频器也需…

openvidu私有化部署

openvidu私有化部署 简介 OpenVidu 是一个允许您实施实时应用程序的平台。您可以从头开始构建全新的 OpenVidu 应用程序&#xff0c;但将 OpenVidu 集成到您现有的应用程序中也非常容易。 OpenVidu 基于 WebRTC 技术&#xff0c;允许开发您可以想象的任何类型的用例&#xf…

回归预测|基于黏菌优化LightGBM的数据回归预测Matlab程序SMA-LightGBM 多特征输入单输出

回归预测|基于黏菌优化LightGBM的数据回归预测Matlab程序SMA-LightGBM 多特征输入单输出 文章目录 前言回归预测|基于黏菌优化LightGBM的数据回归预测Matlab程序 多特征输入单输出 SMA-LightGBM 一、SMA-LightGBM模型1. **LightGBM**2. **黏菌智能优化算法&#xff08;SMA&…

知识中台是什么?它如何实现高效知识管理?

引言 在信息化浪潮席卷全球的今天&#xff0c;企业面临的不仅是市场的激烈竞争&#xff0c;更是知识爆炸带来的管理挑战。如何在浩瀚的信息海洋中提炼出有价值的知识&#xff0c;并将其快速转化为企业的核心竞争力&#xff0c;成为了每个企业必须深思的问题。在此背景下&#…

二叉树的重要概念

前言&#xff1a; 二叉树是树形结构的一个重要类型&#xff0c;一般的树也可以转化成二叉树来解决问题。在数据结构的系统中&#xff0c;树形结构也是信息存储和遍历的重要实现&#xff0c;二叉树的最大特点就是一个根包含着左右子树的形式&#xff0c;许多具有层次关系的问题…

单元测试注解:@ContextConfiguration

ContextConfiguration注解 ContextConfiguration注解主要用于在‌Spring框架中加载和配置Spring上下文&#xff0c;特别是在测试场景中。 它允许开发者指定要加载的配置文件或配置类的位置&#xff0c;以便在运行时或测试时能够正确地构建和初始化Spring上下文。 基本用途和工…

【开源社区】Elasticsearch(ES)中空值字段 null_value 及通过exists查找非空文档

文章目录 0、声明1、问题描述2、问题剖析2.1 NULL或者空值类型有哪些2.2 案例讲解&#xff1a;尝试检索值为 null 的字段2.3 解决思路 3、使用 null_value 的诸多坑&#xff08;避免生产事故&#xff09;3.1 null_value 替换的是索引&#xff0c;并不会直接替换源数据3.2 不支持…

LVS(Linux Virtual Server)详解

LVS&#xff08;Linux Virtual Server&#xff09;是一个用于负载均衡的开源软件项目&#xff0c;旨在通过集群技术实现高性能、高可用的服务器系统。它运行在Linux操作系统上&#xff0c;并且可以利用内核级的资源来提高性能和稳定性。 思维导图 LVS的工作原理 LVS主要基于Ne…

IDEA 2022.1.4用前需知

目录 一、配置国内源 二、正确再次创建新项目方式 IDEA 2022.1.4下载地址 一、配置国内源 1、查看本地仓库地址 2、设置国内源-添加Setting.xml文件内容 3、修改目录&#xff08;考虑到当前硬盘空间大小&#xff0c;英文目录名&#xff09; 1&#xff09;创建你要移动过去…

xCat部署及分发操作系统

一、环境准备 此次安装部署均在VMware虚拟机上运行。系统采用通用稳定的centos7系统,移植到其他(linux)系统应该问题不大。软件服务器的VMware虚拟机的创建部分就跳过了. 1.1服务器的配置 IP主机名配置备注192.168.11.10master4C/8G/60GXcat/DNS/DHCP/NTP/TFTP192.168.11.11n…

【超音速专利 CN109636858A】锂电池涂布图像采集标定方法、系统、设备及存储介质

申请号CN201811276578.4公开号&#xff08;公开&#xff09;CN109636858A申请日2018.10.30申请人&#xff08;公开&#xff09;广州超音速自动化科技股份有限公司(超音速人工智能科技股份有限公司)发明人&#xff08;公开&#xff09;赵兵锁(张); 张俊峰(张); 梁土伟 相关术语…