SpringCloud Alibaba微服务实战三十七 - Oauth2自定义登录接口

Posted 飘渺Jam

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud Alibaba微服务实战三十七 - Oauth2自定义登录接口相关的知识,希望对你有一定的参考价值。

大家好,我是飘渺。(今天又来给大家送书了~

有不少人私下问我,为什么SpringCloud alibaba实战系列不更新了,主要是因为大部分核心功能都已经讲完了,剩下的基本是属于业务功能开发了,需要根据实际业务扩展。

今天更新文章的原因是粉丝提了个问题:如何实现Oauth2认证服务器自定义登录接口以及返回自定义格式? 这里我给大家分享一个简单且实用的方法,既可以灵活定制登录参数也可以自行组装返回结果。

实现方案

我们知道,认证服务器生成token的入口是TokenEndpoint#postAccessToken(Principal principal, @RequestParam Map<String, String> parameters),那我们就可以直接在认证服务器自定义一个登录接口,然后组装好TokenEndpoint#postAccessToken()需要的参数,直接调用它生成token后再封装成我们需要的格式即可。

接下来我们直接进入实战:

1. 定义登录参数

/**
 * 自定义登录参数
 * @author JAVA日知录
 * @date 2022/5/14 09:23
 */
@Data
public class LoginRequest 
    private String userName;
    private String password;
    private String grantType;
    private String mobile;
    private String smsCode;

为了兼容密码模式和自定义的短信验证码模式,我们将所有的参数都放入一个实体,大家可以根据自己的项目需要自行封装。

2. 创建一个登录类型的枚举

public enum LoginTypeEnum 

    /**
     * 密码模式
     */
    PASSWORD("password"),
    /**
     * 短信验证码模式
     */
    SMSCODE("sms_code");

    private final String grantType;

    LoginTypeEnum(String grantType) 
        this.grantType = grantType;
    

    public String getGrantType() 
        return grantType;
    

    public static LoginTypeEnum fromGrantType(String grantType)
        return Arrays.stream(LoginTypeEnum.values())
                .filter(item -> item.getGrantType().equals(grantType))
                .findFirst()
                .orElseThrow(()-> new BizException("不支持此登录类型"));
    

3. 创建自定义登录接口(关键)

@RestController
@RequestMapping("/token")
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AuthController 

    private final TokenStore tokenStore;

    private final TokenEndpoint tokenEndpoint;

    private final RedisTemplate<String,String> redisTemplate;


    /**
     * 自定义登录接口
     * @return
     */
    @PostMapping("login")
    public ResultData<OAuth2AccessToken> login(@RequestBody LoginRequest loginRequest) throws HttpRequestMethodNotSupportedException 
        Assert.isTrue(StringUtils.isNotEmpty(loginRequest.getGrantType()), "请在参数中指定登录类型grantType");

        LoginTypeEnum typeEnum = LoginTypeEnum.fromGrantType(loginRequest.getGrantType());

        //注入clientId 和 password
        // 可以通过Header传入client 和 secret
        User clientUser = new User("jianzh5", "jianzh5", new ArrayList<>());
        Authentication token = new UsernamePasswordAuthenticationToken(clientUser, null, new ArrayList<>());

        //构建密码登录
        Map<String, String> map = new HashMap<>();

        switch (typeEnum)
            case PASSWORD : 
                map.put("username", loginRequest.getUserName());
                map.put("password", loginRequest.getPassword());
                map.put("grant_type", "password");
                break;
            
            case SMSCODE:
                map.put("smsCode", loginRequest.getSmsCode());
                map.put("mobile", loginRequest.getMobile());
                map.put("grant_type", "sms_code");
                break;
            
            default: throw new BizException("不支持的登录类型");
        

        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(token,map).getBody();
        return ResultData.success(oAuth2AccessToken);

    
  ...

这里我们将TokenEndpoint注入,然后伪装一个客户端的认证流程,调用TokenEndpoint.postAccessToken()获取接口。

这里我们写死了client信息,实际上也可以通过Header请求头传入或者通过配置文件注入。

4. 在安全配置类中放行登录接口

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

  ...
    @Override
    protected void configure(HttpSecurity http) throws Exception 
        // 加入验证码登陆
        http.apply(smsCodeSecurityConfig);

        http
                .authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
                .and()
                .authorizeRequests().antMatchers("/token/**","/sms/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf()
                .disable();
    


    @Override
    public void configure(WebSecurity web) throws Exception 
        web.ignoring().antMatchers(
                "/error",
                "/static/**",
                "/v2/api-docs/**",
                "/swagger-resources/**",
                "/webjars/**",
                "/favicon.ico"
            );
    

这个安全配置类中有两个放行策略,一个通过permitAll()实现,一个通过web.ignoring()实现,他们两个的区别是:

web ignoring()比较适合配置前端相关的静态资源,它是完全绕过spring security的所有filter的;permitAll(),会给没有登录的用户适配一个AnonymousAuthenticationToken,设置到SecurityContextHolder,方便后面的filter可以统一处理authentication。


"userName":"zhangjian",
"password":"111111",
"grantType":"password",
"mobile":"18888887777",
"smsCode":"666666"

测试

1. 自定义密码登录


2. 自定义短信验证码登录


3. 不支持的登录类型


小结

本文提供的方案是将登录接口与认证服务器放在一起,如果在项目中由于某些原因不方便将其放在认证服务中,也可以让认证服务器提供一个Feign接口,然后让后端服务调用此接口进行登录即可。

好了,今天就到这儿吧,我是飘渺,我们下期见~~

文末送书

看到这里的同学先别急着关掉,今天联合清华大学出版社又来给大家送书了,这次给大家带来两本《Spring Security实战》

本书是一本关于 Spring Security 的应用指南,主要讲解了 Spring Security 的基础知识点、核心概念,以及围绕身份验证和授权过程的关键处理流程。书中采取了循序渐进、示例辅助的编写方式,以期让读者能够轻松入门并且随着对本书的深入阅读而能稳步得到技能提升,同时也逐渐加深对于 Spring Security 和身份验证以及授权过程的理解。相信在通读并深刻理解本书的内容之后,读者就能够熟练运用 Spring Security对应用程序的各层进行安全配置。本书提供了许多应用示例,并且根据内容结构的编排还提供了 3 个动手实践的练习章节,这样,读者就能通过每一章的知识内容并且结合实践练习来巩固所学知识。

怎么送?

老规矩,在本文留言并将此文转发至朋友圈,从精选留言中随机抽取两位同学,然后凭借朋友圈转发记录领取,给你免费包邮到家!当然,不差钱的也可以通过下方链接直接购买。

以上是关于SpringCloud Alibaba微服务实战三十七 - Oauth2自定义登录接口的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud Alibaba微服务实战三十一 - 业务日志组件

SpringCloud Alibaba微服务实战三十三 - 集成灰度发布

SpringCloud Alibaba微服务实战三十四 - 隐私接口禁止外部访问

SpringCloud Alibaba微服务实战三十六 - 这大概算是使用Feign的正确姿势。

SpringCloud Alibaba微服务实战三十七 - Oauth2自定义登录接口

SpringCloud Alibaba微服务实战三十七 - Oauth2自定义登录接口