上一节实现了,用户名密码+验证码的 json 格式登陆。这一节,需求继续升级了。
需求描述
要求此接口可实现:
- 用户名密码+验证码登陆
- 手机号+验证码登陆
- 第三方Id登陆
解决方案
这一节,我们就要使用,上一章提到的自定义身份验证令牌类了。
在认证入口类(MyAuthenticationProcessingFilter
)解析出登陆参数(LoginData
),然后包装为认证令牌类(MyAuthenticationToken
),继续往下传递。到达认证功能提供类(MyAuthenticationTokenProvider
)中,进行登陆方式判定,然后分别调用不同的方法进行登陆认证。
并且,我们需要扩展登陆参数类。上一节,我们只有三个参数:username
、password
、commonLoginVerifyCode
。
根据需求,我们需要扩充为:
@Data
public class LoginData {
/** 登陆方式 */
private String loginType;
/** 用户名 */
private String username;
/** 密码 */
private String password;
/** 普通登陆验证码 */
private String commonLoginVerifyCode;
/** 手机号 */
private String phone;
/** 手机验证码 */
private String phoneVerifyCode;
/** 第三方平台类型 */
private String thirdPlatformType;
/** 第三方平台id */
private String thirdPlatformId;
}
复制代码
实现
无关逻辑均做了简化处理。
-
自定义认证类
模仿
UsernamePasswordAuthenticationToken
编写。@Getter @Setter @ToString public class MyAuthenticationToken extends AbstractAuthenticationToken { private final Object principal; private Object credentials; private LoginData loginData; public MyAuthenticationToken(Object principal, Object credentials) { super(null); this.principal = principal; this.credentials = credentials; setAuthenticated(false); } public MyAuthenticationToken(Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; // must use super, as we override super.setAuthenticated(true); } @Override public Object getCredentials() { return this.credentials; } @Override public Object getPrincipal() { return this.principal; } @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { if (isAuthenticated) { throw new IllegalArgumentException( "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } super.setAuthenticated(false); } @Override public void eraseCredentials() { super.eraseCredentials(); credentials = null; } 复制代码
-
自定义 Provider
模仿
DaoAuthenticationProvider
编写。@Component @Slf4j public class MyAuthenticationTokenProvider extends AbstractUserDetailsAuthenticationProvider { @Autowired MyUserDetailServiceImpl myUserDetailService; @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {} // 认证逻辑 @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { MyAuthenticationToken myAuthenticationToken = (MyAuthenticationToken) authentication; log.info("{}",myAuthenticationToken.toString()); LoginData loginData = myAuthenticationToken.getLoginData(); if (loginData == null) { throw new AuthenticationServiceException("未获取到登陆参数"); } String loginType = loginData.getLoginType(); if (loginType == null || loginType.equals("")) { throw new AuthenticationServiceException("登陆方式不可为空"); } UserDetails userDetails; if (LoginType.USERNAME_CODE.equals(loginType)) { // 用户名密码登陆 log.info("尝试以 {} 方式登陆",LoginType.USERNAME_CODE); this.checkUsernameCode(loginData.getUsername(),loginData.getCommonLoginVerifyCode()); userDetails = myUserDetailService.loadUserByUsername(loginData.getUsername()); }else if (LoginType.PHONE_CODE.equals(loginType)) { // 手机号验证码登陆 log.info("尝试以 {} 方式登陆",LoginType.PHONE_CODE); this.checkPhoneCode(loginData.getPhone(),loginData.getPhoneVerifyCode()); userDetails = myUserDetailService.loadUserByPhone(loginData.getPhone()); }else if (LoginType.THIRD_PLATFORM.equals(loginType)) { // 三方平台登陆 log.info("尝试以 {} 方式登陆",LoginType.THIRD_PLATFORM); userDetails = myUserDetailService.loadByThirdPlatformId(loginData.getThirdPlatformType(),loginData.getThirdPlatformId()); }else { throw new AuthenticationServiceException("不支持的登陆方式"); } // 认证成功 return this.createSuccessAuthentication(userDetails,myAuthenticationToken,userDetails); } @Override protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { return null; } // 此 provider 支持什么类型的令牌类 @Override public boolean supports(Class<?> authentication) { return (MyAuthenticationToken.class.isAssignableFrom(authentication)); } public void checkPhoneCode(String phone,String code) { // todo 校验手机验证码 if ("111111".equals(code)) { }else { throw new AuthenticationServiceException("手机验证码错误"); } } public void checkUsernameCode(String codeId,String code) { // todo 校验用户名密码登陆 if ("222222".equals(code)) { }else { throw new AuthenticationServiceException("验证码错误"); } } } 复制代码
-
修改 Security 配置
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired MyUserDetailServiceImpl userDetailService; @Autowired BCryptPasswordEncoder passwordEncoder; @Autowired MyAuthenticationTokenProvider myAuthenticationTokenProvider; // 创建自己的AuthenticationManager @Override @Bean protected AuthenticationManager authenticationManager() throws Exception { ProviderManager manager = new ProviderManager(Collections.singletonList(myAuthenticationTokenProvider)); return manager; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailService); } @Bean MyAuthenticationProcessingFilter myAuthenticationProcessingFilter() throws Exception { MyAuthenticationProcessingFilter filter = new MyAuthenticationProcessingFilter(); filter.setAuthenticationSuccessHandler((request, response, authentication) -> { response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); MyUserDetails userDetails = (MyUserDetails) authentication.getPrincipal(); userDetails.setPassword(null); HashMap<String, Object> result = new HashMap<>(); result.put("code","000000"); result.put("msg","登陆成功"); result.put("data",userDetails); String s = new ObjectMapper().writeValueAsString(result); out.write(s); out.flush(); out.close(); }); filter.setAuthenticationFailureHandler((request, response, exception) -> { response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); HashMap<String, Object> result = new HashMap<>(); result.put("code","111111"); if (exception instanceof LockedException) { result.put("msg","账户被锁定!"); } else if (exception instanceof DisabledException) { result.put("msg","账户被禁用,请联系管理员!"); } else if (exception instanceof BadCredentialsException) { result.put("msg","用户名或者密码输入错误,请重新输入!"); } else if (exception instanceof AuthenticationServiceException) { result.put("msg",exception.getMessage()); } out.write(new ObjectMapper().writeValueAsString(result)); out.flush(); out.close(); }); // 把自己的authenticationManager 设置到环境中 filter.setAuthenticationManager(authenticationManager()); filter.setFilterProcessesUrl("/toLogin"); return filter; } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest() .authenticated() .and() .csrf() .disable(); // 替换认证拦截器(认证入口) http.addFilterAt(myAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class); } } 复制代码
登陆
-
不配置登陆方式
request
{ "phone":"987654321", "phoneVerifyCode":"111111" } 复制代码
resp
{ "msg": "登陆方式不可为空", "code": "111111" } 复制代码
-
手机号验证码登陆
request
{ "loginType":"phoneCode", "phone":"987654321", "phoneVerifyCode":"111111" } 复制代码
resp
{ "msg": "登陆成功", "code": "000000", "data": { "id": 2, "phone": null, "username": "李四", "password": null, "accountStatus": 1, "roleList": [ { "id": 1, "name": "admin", "desc": null }, { "id": 2, "name": "user", "desc": null } ], "enabled": true, "authorities": [ { "authority": "admin" }, { "authority": "user" } ], "accountNonExpired": true, "credentialsNonExpired": true, "accountNonLocked": true } } 复制代码
-
用户名密码登陆
request
{ "loginType":"usernameCode", "username":"张三", "password":"123456", "commonLoginVerifyCode":"222222"} 复制代码
resp
{ "msg": "登陆成功", "code": "000000", "data": { "id": 1, "phone": null, "username": "张三", "password": null, "accountStatus": 1, "roleList": [ { "id": 1, "name": "admin", "desc": null }, { "id": 2, "name": "user", "desc": null } ], "enabled": true, "authorities": [ { "authority": "admin" }, { "authority": "user" } ], "accountNonExpired": true, "credentialsNonExpired": true, "accountNonLocked": true }} 复制代码
-
第三方openId登陆
request
{ "loginType":"thirdPlatform", "thirdPlatformType":"WeChat", "thirdPlatformId":"qwqwqqwqwqw"} 复制代码
resp
{ "msg": "登陆成功", "code": "000000", "data": { "id": 1, "phone": null, "username": "张三", "password": null, "accountStatus": 1, "roleList": [ { "id": 1, "name": "admin", "desc": null }, { "id": 2, "name": "user", "desc": null } ], "enabled": true, "authorities": [ { "authority": "admin" }, { "authority": "user" } ], "accountNonExpired": true, "credentialsNonExpired": true, "accountNonLocked": true }} 复制代码
这里都是比较简单的demo,实际运用过程中,还需要优化。如,针对多种登陆方式,我们可以提供多种 provider 来进行处理。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END