shiro主要有用户认证和用户授权两个功能
一、用户认证
1、导入依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
2、新增测试页面
新增测试页面:
login.html(登陆页面)、index.html(登陆成功页面)、error.html (无权限页面)、add.html(添加页面)、update.html(修改页面)
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>登录页面</title> </head> <body> <h1>登录页面</h1> <hr/> <h3 th:text="${msg}" style="color: red"></h3> <form method="post" action="login"> 用户名 :<input type="text" name="username"><br> 密 码 :<input type="password" name="password"><br> <input type="submit" value="登录"> </form> </body> </html>
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>测试Thymeleaf</title> </head> <body> <h3 th:text="${test}"></h3> <hr/> 进入用户添加页面:<a href="add">用户添加</a> <br> 进入用户修改页面:<a href="update">用户修改</a> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>无权限</title> </head> <body> <h1>无权限页面</h1> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户添加页面</title> </head> <body> <h1>用户添加页面</h1> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户修改页面</title> </head> <body> <h1>用户修改页面</h1> </body> </html>
3、新增控制类
新增控制类,包含登陆方法(login)、跳转登陆页面方法(toLogin)、添加用户页面(add)、修改用户页面(update)、不被拦截的方法(hello)
其中需要注意的就是登陆方法(login),所有登录校验都交给shiro框架处理。
package com.example.demo.controller; import com.example.demo.resource.ShiroConfig; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; @Controller public class LoginController { private Logger logger = LoggerFactory.getLogger(ShiroConfig.class); @ResponseBody @GetMapping(value = "/hello") public String hello(){ logger.info("不登陆也能访问"); return "hello不登陆也能访问"; } @RequestMapping(value = "/index") public String testThymeleaf(Model model) { //把数据存入model model.addAttribute("test", "测试Thymeleaf"); //返回test.html return "/index"; } @RequestMapping(value = "/add") public String add(Model model) { //把数据存入model model.addAttribute("test", "添加用户页面"); //返回test.html return "/user/add"; } @RequestMapping(value = "/update") public String update(Model model) { //把数据存入model model.addAttribute("test", "修改用户页面"); //返回test.html return "/user/update"; } @RequestMapping(value = "/toLogin") public String toLogin(){ logger.info("跳转登陆页面"); return "/login"; } @PostMapping(value = "login") public String login(String username,String password, Model model){ logger.info("登陆用户名【{}】,密码【{}】",username,password); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username,password); try{ subject.login(token); }catch (UnknownAccountException e){ logger.error("对用户[{}]进行登录验证,验证未通过,用户不存在", username); token.clear(); return "login"; }catch (LockedAccountException lae) { logger.error("对用户[{}]进行登录验证,验证未通过,账户已锁定", username); token.clear(); return "login"; } catch (ExcessiveAttemptsException e) { logger.error("对用户[{}]进行登录验证,验证未通过,错误次数过多", username); token.clear(); return "login"; } catch (AuthenticationException e) { logger.error("对用户[{}]进行登录验证,验证未通过,堆栈轨迹如下", username, e); token.clear(); return "login"; } return "/index"; } }
4、(重点)创建自己的认证类
认证类需要继承AuthorizingRealm类,并重写认证和授权)两个方法。
认证方法(doGetAuthenticationInfo):该方法在登陆时调用,用来做登录校验
授权方法(doGetAuthorizationInfo):该方法在需要验证权限时调用,用来判断是否拥有权限
此处先校验登陆问题,因此先只处理认证方法,授权问题详见第二点。在认证方法中,查询了数据库与前台传入的用户名、密码进行校验,此处的查询数据库的一系列文件(service、serviceImpl、mapper、dao、数据库建表等)不再演示。
package com.example.demo.utils; import com.example.demo.entity.User; import com.example.demo.service.UserService; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import java.util.*; /** * 认证 */ @Configuration public class AuthRealm extends AuthorizingRealm { Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private UserService userService; /** * 只有需要验证权限时才会调用, 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.在配有缓存的情况下,只加载一次. * 如果需要动态权限,但是又不想每次去数据库校验,可以存在ehcache中.自行完善 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("用户授权"); return null; } /** * 认证回调函数,登录时调用 * 首先根据传入的用户名获取User信息;然后如果user为空,那么抛出没找到帐号异常UnknownAccountException; * 如果user找到但锁定了抛出锁定异常LockedAccountException;最后生成AuthenticationInfo信息, * 交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配, * 如果不匹配将抛出密码错误异常IncorrectCredentialsException; * 另外如果密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException; * 在组装SimpleAuthenticationInfo信息时, 需要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt), * CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { logger.info("用户认证"); UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; User user = userService.selectOneByUsername(usernamePasswordToken.getUsername()); //2、判断用户名称是否存在 if (null == user || !user.getUsername().equals(usernamePasswordToken.getUsername())) { //用户名称不存在,Shiro底层会抛出UnknowAccountException return null; } //3、判断密码是否正确 return new SimpleAuthenticationInfo((String)token.getPrincipal(), user.getPassword(), user.getUsername()); } }
5、(重点)配置shiro配置类
shiro配置类里面主要有三点:
(1)获取自定义的认证类
(2)获取带有认证类(自定义的认证类)的安全管理器(SecurityManager)
(3)获取带有安全管理器的过滤器
关于过滤器,主要时配置拦截规则,以及各种情况跳转的页面信息。其中要注意两点,一定要把resource下的所有文件置为无需拦截,否则就会发生虽然方法没有被拦截,但是由于页面被拦截而跳转登陆页面的情况;还有一个就是filterMap的最后一定要要把/**职位需要验证。
package com.example.demo.resource; import com.example.demo.utils.AuthRealm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration public class ShiroConfig { private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class); @Bean public AuthRealm getAuthRealm(){ return new AuthRealm(); } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(AuthRealm authRealm){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(authRealm); return defaultWebSecurityManager; } @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); /* 内置Shiro过滤器实现相关拦截功能 常用的过滤器有: anon : 无需认证(登录)可以访问 authc : 必须认证才能访问 user : 如果使用rememberMe的功能可以直接访问 perms : 该资源必须得到资源访问权限才可以使用 role : 该资源必须得到角色授权才可以使用*/ Map<String,String> filterMap = new HashMap<>(); filterMap.put("/resource/**","anon"); filterMap.put("/install","anon"); filterMap.put("/hello","anon"); filterMap.put("/login","anon"); //filterMap.put("/add","perms[user:add]"); filterMap.put("/**","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//未登录跳转登陆页面 shiroFilterFactoryBean.setLoginUrl("/toLogin");
//认证成功跳转URL shiroFilterFactoryBean.setSuccessUrl("/test");
//登录后无权限URL shiroFilterFactoryBean.setUnauthorizedUrl("/error"); return shiroFilterFactoryBean; } }
6、测试
此处测试分为以下几点:
二、资源授权
第一点中的第5步中关于shiro配置类的代码中有一行代码是注释掉的(filterMap.put("/add","[user:add]");),这个就是对于资源的授权,这一行的代码就是表明访问add时需要user:add权限,放开该行注释后,再点击添加和修改,可以看到用户修改可以正常跳转,但是新增用户就会跳转到我们设置的无权限页面。
所以就需要对用户进行资源的授权。
1、导入依赖
<!-- thymeleaf 扩展 shiro--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
2、前端页面添加shiro标签
此处引入了shiro标签xmlns:shiro="http://www.w3.org/1999/xhtml",然后在连接处使用了该标签 hiro:hasPermission
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:shiro="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>测试Thymeleaf</title> </head> <body> <h3 th:text="${test}"></h3> <hr/> <div shiro:hasPermission="user:add"> 进入用户添加页面:<a href="add">用户添加</a> </div> <br> <div shiro:hasPermission="user:update"> 进入用户修改页面:<a href="update">用户修改</a> </div> </body> </html>
3、修改AuthRealm类中的授权方法
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("用户授权"); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); Subject subject = SecurityUtils.getSubject(); String username = (String)subject.getPrincipal(); User user = userService.selectOneByUsername(username); simpleAuthorizationInfo.addStringPermission(user.getPerms()); return simpleAuthorizationInfo; }
4、数据库调整
为admin用户添加新增权限,为lcl用户添加修改权限
5、测试
使用admin用户登陆,则只能看到新增按钮,使用lcl用户登陆,则只能看到update按钮。
关于springBoot整合Shiro就整合完毕了,其中有几点不合理的地方,为了演示整合,就没有写的那么详细,比如说,一般情况下,会使用用户、角色、权限三张表来做权限处理、会使用缓存来对用户信息进行缓存等。