AI智能
改变未来

SpringBoot整合Shiro实现密码/验证码登录(多Realm认证)


shiro安全框架简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。

三个核心组件:Subject, SecurityManager 和 Realms.

  • Subject:代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
  • SecurityManager:它是Shiro框架的核心,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
  • Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。
  • 导入依赖(pom.xml)

<!--整合Shiro安全框架--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><!--集成jwt实现token认证--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.2.0</version></dependency>
  • 创建 ShiroConfig 配置类

@Configurationpublic class ShiroConfig {/*** ShiroFilterFactoryBean* <p>* anon:无需认证就可以访问* authc:必须认证才能访问* user:必须拥有 记住我 功能才能用* perms:拥有对某个资源的权限能访问* role:拥有某个角色权限能访问*/@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier(\"securityManager\") DefaultWebSecurityManager defaultWebSecurityManager) {ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();// 设置安全管理器factoryBean.setSecurityManager(defaultWebSecurityManager);// 添加shiro的内置过滤器Map<String, String> filterMap = new LinkedHashMap<>();// 放行不需要权限认证的接口// 网站首页filterMap.put(\"/\", \"anon\");filterMap.put(\"/index\", \"anon\");filterMap.put(\"/index.html\", \"anon\");// 不验证跳转接口filterMap.put(\"/into/**\", \"anon\");// 需要权限认证的接口// 验证跳转接口filterMap.put(\"/verifyInto/**\", \"authc\");factoryBean.setFilterChainDefinitionMap(filterMap);// 访问没有授权的资源factoryBean.setLoginUrl(\"redirect:/into/login\");// 设置无权限时跳转的urlfactoryBean.setUnauthorizedUrl(\"redirect:/into/login\");return factoryBean;}/*** 管理shiro的生命周期*/@Bean(\"lifecycleBeanPostProcessor\")public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** 注入 密码登录CustomRealm*/@Bean@DependsOn(\"lifecycleBeanPostProcessor\")public UserPasswordRealm userPasswordRealm() {return new UserPasswordRealm();}/*** 注入 邮箱验证登录EmailRealm*/@Bean@DependsOn(\"lifecycleBeanPostProcessor\")public UserEmailRealm userEmailRealm() {return new UserEmailRealm();}/*** 默认安全管理器*/@Beanpublic DefaultWebSecurityManager securityManager(UserPasswordRealm userPasswordRealm, UserEmailRealm userEmailRealm, AbstractAuthenticator abstractAuthenticator) {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();List<Realm> realms = new ArrayList<>();realms.add(userPasswordRealm);realms.add(userEmailRealm);defaultWebSecurityManager.setRealms(realms);// 记住我defaultWebSecurityManager.setRememberMeManager(cookieRememberMeManager());defaultWebSecurityManager.setAuthenticator(abstractAuthenticator);return defaultWebSecurityManager;}/*** 认证器 把我们的自定义验证加入到认证器中*/@Beanpublic AbstractAuthenticator abstractAuthenticator(UserPasswordRealm userPasswordRealm, UserEmailRealm userEmailRealm) {// 自定义模块化认证器,用于解决多realm抛出异常问题//开始没用自定义异常问题,发现不管是账号密码错误还是什么错误//shiro只会抛出一个AuthenticationException异常ModularRealmAuthenticator authenticator = new MyCustomModularRealmAuthenticator();// 认证策略:AtLeastOneSuccessfulStrategy(默认),AllSuccessfulStrategy,FirstSuccessfulStrategyauthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());// 加入realmsList<Realm> realms = new ArrayList<>();realms.add(userPasswordRealm);realms.add(userEmailRealm);authenticator.setRealms(realms);return authenticator;}/*** 加入shiro注解  代理生成器 切面*/@Bean@DependsOn({\"lifecycleBeanPostProcessor\"})public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}/*** 加入shiro注解 切点*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** 设置cookie 记住我生成cookie*/@Beanpublic CookieRememberMeManager cookieRememberMeManager() {CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());return cookieRememberMeManager;}/*** 设置cookie有效时间*/@Beanpublic SimpleCookie rememberMeCookie() {/*这个参数是cookie的名称,对应前端页面的checkbox的name=remremberMe*/SimpleCookie simpleCookie = new SimpleCookie(\"rememberMe\");/*cookie的有效时间为30天,单位秒*/simpleCookie.setMaxAge(259200);return simpleCookie;}}
  • 创建自定义验证器MyCustomModularRealmAuthenticator 类

public class MyCustomModularRealmAuthenticator extends ModularRealmAuthenticator {@Overrideprotected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {AuthenticationStrategy authenticationStrategy = this.getAuthenticationStrategy();AuthenticationInfo authenticationInfo = authenticationStrategy.beforeAllAttempts(realms, token);Iterator var5 = realms.iterator();while (var5.hasNext()) {Realm realm = (Realm) var5.next();authenticationInfo = authenticationStrategy.beforeAttempt(realm, token, authenticationInfo);if (realm.supports(token)) {AuthenticationInfo info = null;Throwable t = null;info = realm.getAuthenticationInfo(token);authenticationInfo = authenticationStrategy.afterAttempt(realm, token, info, authenticationInfo, t);}}authenticationInfo = authenticationStrategy.afterAllAttempts(token, authenticationInfo);return authenticationInfo;}}
  • 创建密码登录时验证授权UserPasswordRealm 类

@Componentpublic class UserPasswordRealm extends AuthorizingRealm {// 注入用户业务@Autowiredprivate UserMapper userMapper;/*** 授权*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {System.out.println(\"————密码授权————doGetAuthorizationInfo————\");return null;}/*** 认证*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println(\"————密码认证————doGetAuthenticationInfo————\");UsernamePasswordToken userToken = (UsernamePasswordToken) token;// 连接数据库  查询用户数据QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq(\"user_name\", userToken.getUsername());User user = userMapper.selectOne(wrapper);// 验证用户if (user == null) {throw new UnknownAccountException();}return new SimpleAuthenticationInfo(\"\", user.getUserPassword(), \"\");}/*** 用来判断是否使用当前的 realm** @param var1 传入的token* @return true就使用,false就不使用*/@Overridepublic boolean supports(AuthenticationToken var1) {return var1 instanceof UsernamePasswordToken;}}
  • 创建邮件验证码登录时验证授权UserEmailRealm类

@Componentpublic class UserEmailRealm extends AuthorizingRealm {// 注入用户业务@AutowiredUserService userService;@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println(\"————邮箱登录授权————doGetAuthorizationInfo————\");return null;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println(\"————邮箱登录认证————doGetAuthenticationInfo————\");UserEmailToken userEmailToken = (UserEmailToken) token;String userEmail = (String) userEmailToken.getPrincipal();// 连接数据库  查询用户数据QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq(\"user_email\", userEmail);User user = userService.getOne(wrapper);//因为没有密码,并且验证码在之前就验证了if (user == null) {throw new UnknownAccountException();}return new SimpleAuthenticationInfo(\"\", userEmail, \"\");}/*** 用来判断是否使用当前的 realm** @param var1 传入的token* @return true就使用,false就不使用*/@Overridepublic boolean supports(AuthenticationToken var1) {return var1 instanceof UserEmailToken;}}
  • 创建邮件验证码登录验证通过生成令牌的 UserEmailToken 类(密码登录时使用shiro默认的 UsernamePasswordToken 令牌)

@Data  // 使用lombok 生成get方法、set方法public class UserEmailToken implements HostAuthenticationToken, RememberMeAuthenticationToken {private String userEmail;private boolean rememberMe;private String host;public UserEmailToken() {this.rememberMe = false;}public UserEmailToken(String userEmail) {this(userEmail, false, null);}public UserEmailToken(String userEmail, boolean rememberMe) {this(userEmail, rememberMe, null);}public UserEmailToken(String userEmail, boolean rememberMe, String host) {this.userEmail = userEmail;this.rememberMe = rememberMe;this.host = host;}@Overridepublic String getHost() {return host;}@Overridepublic boolean isRememberMe() {return rememberMe;}/*** 重写getPrincipal方法*/@Overridepublic Object getPrincipal() {return userEmail;}/*** 重写getCredentials方法*/@Overridepublic Object getCredentials() {return userEmail;}}
  • 创建密码盐值加密MDPasswordUtil工具类

public class MDPasswordUtil {public String getMDPasswordUtil(String userName, String userPassword) {String hashAlgorithmName = \"MD5\";  // 加密方式:md5加密Object credentials = userPassword;  // 密码Object salt = ByteSource.Util.bytes(userName); // 盐int hashIterations = 512;  // 加密次数Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);return result.toString();}}
  • 控制层用户密码登录

// 用户密码登录@PostMapping(\"/passwordLogin\")public String userLogin(@RequestParam(\"userName\") String userName,@RequestParam(\"userPassword\") String userPassword,HttpSession session, Model model) {// 获取当前的用户Subject subject = SecurityUtils.getSubject();// 对密码进行MD5盐值加密String md5Password = new MDPasswordUtil().getMDPasswordUtil(userName, userPassword);// 封装用户的登录数据UsernamePasswordToken token = new UsernamePasswordToken(userName, md5Password);//rememberme记住我token.setRememberMe(true);try {// 登录,验证,保存令牌subject.login(token);//查询登录信息QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq(\"user_name\", userName);User user = userService.getOne(wrapper);//保存登录用户信息session.setAttribute(user.getUserId().toString(), user);return \"admin\";} catch (UnknownAccountException e) {model.addAttribute(\"userError\", \"用户名错误!请重新输入。\");return \"login\";} catch (IncorrectCredentialsException ice) {model.addAttribute(\"pwError\", \"密码错误!请重新输入。\");return \"login\";}}
  • 控制层用户邮件验证码密码登录

// 用户邮箱登录@PostMapping(\"/emailLogin\")public String emailLogin(@RequestParam(\"userEmail\") String userEmail,@RequestParam(\"emailCode\") String emailCode,HttpSession session, Model model) {// 根据userEmail从session中取出发送的验证码String sendEmailCode = (String) session.getAttribute(userEmail);// 比对验证码if (StringUtils.isNoneBlank(sendEmailCode) && sendEmailCode.equals(emailCode)) {try {UserEmailToken token = new UserEmailToken(userEmail);//rememberme记住我token.setRememberMe(true);// 登录,验证,保存令牌Subject subject = SecurityUtils.getSubject();subject.login(token);//查询登录信息QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq(\"user_email\", userEmail);User user = userService.getOne(wrapper);//保存登录用户信息session.setAttribute(user.getUserId().toString(), user);// 销毁验证码session.removeAttribute(emailCode);return \"admin\";} catch (Exception e) {model.addAttribute(\"error\", \"验证码错误!请重新输入。\");return \"login\";}} else {return \"login\";}}
  • SpringBoot 整合 Shiro 密码登录与邮件验证码登录(多 Realm 认证)就可以了 (有点多,哈哈哈)

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » SpringBoot整合Shiro实现密码/验证码登录(多Realm认证)