【Shiro】用户名密码或手机号短信登录(多realm认证)

您所在的位置:网站首页 伊人软件怎么注销号码账号和密码登录 【Shiro】用户名密码或手机号短信登录(多realm认证)

【Shiro】用户名密码或手机号短信登录(多realm认证)

2024-07-10 23:00| 来源: 网络整理| 查看: 265

这里写图片描述 在登录认证中,经常需要实现用户名密码和手机号验证码这两种登录方式。

最近学了Shiro,所以在这里记录下。

用户名密码使用的令牌自然是UsernamePasswordToken,我们可以参考UsernamePasswordToken,自定义PhoneToken,在不同的控制器中传入Token,然后由Realm判断当前的Token属于UsernamePasswordToken还是PhoneToken。

自定义Token: public class PhoneToken implements HostAuthenticationToken, RememberMeAuthenticationToken, Serializable { // 手机号码 private String phone; private boolean rememberMe; private String host; /** * 重写getPrincipal方法 */ public Object getPrincipal() { return phone; } /** * 重写getCredentials方法 */ public Object getCredentials() { return phone; } public PhoneToken() { this.rememberMe = false; } public PhoneToken(String phone) { this(phone, false, null); } public PhoneToken(String phone, boolean rememberMe) { this(phone, rememberMe, null); } public PhoneToken(String phone, boolean rememberMe, String host) { this.phone = phone; this.rememberMe = rememberMe; this.host = host; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String getHost() { return host; } @Override public boolean isRememberMe() { return rememberMe; } } 自定义Realm: public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = null; if(authenticationToken instanceof UsernamePasswordToken){ token = (UsernamePasswordToken) authenticationToken; }else{ return null; } String username = token.getUsername(); if(StringUtils.isBlank(username)){ return null; } UserDO user = userService.getUser(token.getUsername()); // 账号不存在 if (user == null) { throw new CustomAuthenticationException("账号或密码不正确"); } // 账号锁定 if (user.getStatus() == 1) { throw new CustomAuthenticationException("账号已被锁定,请联系管理员"); } // 主体,一般存用户名或用户实例对象,用于在其他地方获取当前认证用户信息 Object principal = user; // 凭证,这里是从数据库取出的加密后的密码,Shiro会用于与token中的密码比对 Object hashedCredentials = user.getPassword(); // 以用户名作为盐值 ByteSource credentialsSalt = ByteSource.Util.bytes(token.getUsername()); return new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, this.getName()); } // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } @Override public boolean supports(AuthenticationToken var1){ return var1 instanceof UsernamePasswordToken; } } public class PhoneRealm extends AuthorizingRealm { @Resource UserService userService; // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { PhoneToken token = null; // 如果是PhoneToken,则强转,获取phone;否则不处理。 if(authenticationToken instanceof PhoneToken){ token = (PhoneToken) authenticationToken; }else{ return null; } String phone = (String) token.getPrincipal(); UserDO user = userService.selectByPhone(phone); if (user == null) { throw new CustomAuthenticationException("手机号错误"); } return new SimpleAuthenticationInfo(user, phone, this.getName()); } // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } @Override public boolean supports(AuthenticationToken var1){ return var1 instanceof PhoneToken; } } 控制器中的使用: @RequestMapping("/user") @Controller public class UserController { // 用户名密码登录 @PostMapping("/dologin") @ResponseBody public BackAdminResult dologin(@RequestParam("username") String username, @RequestParam("password") String password, HttpSession session) throws AuthenticationException { UsernamePasswordToken token = new UsernamePasswordToken(username, password); Subject subject = SecurityUtils.getSubject(); subject.login(token); UserDO user = (UserDO) subject.getPrincipal(); session.setAttribute(Constants.LOGIN_ADMIN_KEY, user); subject.getSession().setAttribute(Constants.LOGIN_ADMIN_KEY, user); return BackAdminResult.build(0, "登录成功!"); } // 使用手机号和短信验证码登录 @RequestMapping("/plogin") @ResponseBody public BackAdminResult pLogin(@RequestParam("phone") String phone, @RequestParam("code") String code, HttpSession session){ // 根据phone从session中取出发送的短信验证码,并与用户输入的验证码比较 String messageCode = (String) session.getAttribute(phone); if(StringUtils.isNoneBlank(messageCode) && messageCode.equals(code)){ UserNamePasswordPhoneToken token = new UserNamePasswordPhoneToken(phone); Subject subject = SecurityUtils.getSubject(); subject.login(token); UserDO user = (UserDO) subject.getPrincipal(); session.setAttribute(Constants.LOGIN_ADMIN_KEY, user); return BackAdminResult.build(0, "登录成功!"); }else{ return BackAdminResult.build(2, "验证码错误!"); } } } 配置(部分): @Configuration public class ShiroConfig { /** * 加密策略 */ @Bean public CredentialsMatcher credentialsMatcher(){ HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); // 加密算法:MD5、SHA1 credentialsMatcher.setHashAlgorithmName(Constants.Hash_Algorithm_Name); // 散列次数 credentialsMatcher.setHashIterations(Constants.Hash_Iterations); return credentialsMatcher; } /** * 自定义Realm */ @Bean public UserRealm userRealm(CredentialsMatcher credentialsMatcher) { UserRealm userRealm = new UserRealm(); userRealm.setCredentialsMatcher(credentialsMatcher); userRealm.setCacheManager(shiroCacheManager()); return userRealm; } @Bean public PhoneRealm phoneRealm(){ PhoneRealm phoneRealm = new PhoneRealm(); phoneRealm.setCacheManager(shiroCacheManager()); return phoneRealm; } /** * 认证器 */ @Bean public AbstractAuthenticator abstractAuthenticator(UserRealm userRealm, PhoneRealm phoneRealm){ // 自定义模块化认证器,用于解决多realm抛出异常问题 ModularRealmAuthenticator authenticator = new CustomModularRealmAuthenticator(); // 认证策略:AtLeastOneSuccessfulStrategy(默认),AllSuccessfulStrategy,FirstSuccessfulStrategy authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); // 加入realms List realms = new ArrayList(); realms.add(userRealm); realms.add(phoneRealm); authenticator.setRealms(realms); return authenticator; } @Bean public SecurityManager securityManager(UserRealm userRealm, PhoneRealm phoneRealm, AbstractAuthenticator abstractAuthenticator) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realms List realms = new ArrayList(); realms.add(userRealm); realms.add(phoneRealm); securityManager.setRealms(realms); // 自定义缓存实现,可以使用redis securityManager.setCacheManager(shiroCacheManager()); // 自定义session管理,可以使用redis securityManager.setSessionManager(sessionManager()); // 注入记住我管理器 securityManager.setRememberMeManager(rememberMeManager()); // 认证器 securityManager.setAuthenticator(abstractAuthenticator); return securityManager; } } 自定义异常: public class CustomAuthenticationException extends AuthenticationException { // 异常信息 private String msg; public CustomAuthenticationException(String msg){ super(msg); this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } 自定义异常处理: /** * 用于捕获和处理Controller抛出的异常 */ @ControllerAdvice public class GlobalExceptionHandler { private final static Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(CustomAuthenticationException.class) @ResponseBody public BackAdminResult handleAuthentication(Exception ex){ LOG.info("Authentication Exception handler " + ex.getMessage() ); return BackAdminResult.build(1, ex.getMessage()); } }

这里有个问题,就是默认的ModularRealmAuthenticator在处理多realm时,会把异常捕获,导致自定义异常处理器捕获不到认证时抛出的异常,所以需要重写ModularRealmAuthenticator的doMultiRealmAuthentication方法,把异常抛出来。

public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator { /** * 重写doMultiRealmAuthentication,抛出异常,便于自定义ExceptionHandler捕获 */ @Override public AuthenticationInfo doMultiRealmAuthentication(Collection realms, AuthenticationToken token) throws AuthenticationException { AuthenticationStrategy strategy = this.getAuthenticationStrategy(); AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token); Iterator var5 = realms.iterator(); while(var5.hasNext()) { Realm realm = (Realm)var5.next(); aggregate = strategy.beforeAttempt(realm, token, aggregate); if (realm.supports(token)) { AuthenticationInfo info = null; Throwable t = null; info = realm.getAuthenticationInfo(token); aggregate = strategy.afterAttempt(realm, token, info, aggregate, t); } } aggregate = strategy.afterAllAttempts(token, aggregate); return aggregate; } }

在配置那里把自定义的ModularRealmAuthenticator替换默认的即可。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3