大家好,我是飘渺。(今天又来给大家送书了~)
有不少人私下问我,为什么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接口,然后让后端服务调用此接口进行登录即可。
好了,今天就到这儿吧,我是飘渺,我们下期见~~