首先贴上官网地址https://shiro.apache.org/spring-framework.html
一、shiro配置类
要使用shiro必须配置一个shiroConfig配置类,中shiro配置的类中需要三个bean:
- Realm,自定义的Realm
- DeaultWebSecurityManager,需要传递Realm参数
-
ShiroFilterFactoryBean,需要传递DeaultWebSecurityManager参数
- 在ShiroFilterFactoryBean里面我们可以选择添加对应的过滤器
- anon : 无需认证,就可以访问
- authc : 必须认证,才能访问
- user : 必须拥有 “记住我”功能才能用
- perms : 拥有对某个资源的权限才能访问
- role : 拥有某个角色权限才能访问
- 在ShiroFilterFactoryBean里面我们可以选择添加对应的过滤器
@Configuration
public class ShiroConfig {
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
@Bean
public DefaultWebSecurityManager defaultSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultSecurityManager") DefaultWebSecurityManager SecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(SecurityManager);
//添加 Shiro 的内置过滤器=======================
/*
anon : 无需认证,就可以访问
authc : 必须认证,才能访问
user : 必须拥有 “记住我”功能才能用
perms : 拥有对某个资源的权限才能访问
role : 拥有某个角色权限才能访问
*/
Map<String, String> map = new LinkedHashMap<>();
map.put("/back/**","authc");
map.put("/front/**","anon");
// 设置 /user/addUser 这个请求,只有认证过才能访问
// map.put("/user/addUser","authc");
// map.put("/user/deleteUser","authc");
// 设置 /user/ 下面的所有请求,只有认证过才能访问
// map.put("/user/*","authc");
bean.setFilterChainDefinitionMap(map);
// 设置登录的请求
bean.setLoginUrl("/login");
//============================================
bean.setUnauthorizedUrl("/noPermission");
return bean;
}
//thymeleaf集成shiro
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
@Bean
public ShiroExceptionResolver shiroExceptionResolver(){
return new ShiroExceptionResolver();
}
}
shiro内置的几种过滤器
anon : 无需认证,就可以访问
authc : 必须认证,才能访问
user : 必须拥有 “记住我”功能才能用
perms : 拥有对某个资源的权限才能访问
role : 拥有某个角色权限才能访问
二、shiro-realm认证和授权
自定义的Realm需要集成AuthorizingRealm,重写以下方法
-
doGetAuthorizationInfo(PrincipalCollection principalCollection)
Authorization授权
- 首先doGetAuthorizationInfo需要return一个AuthorizationInfo对象,我们先new一个他的实现类对象:SimpleAuthorizationInfo
- 授权需要我们拿到登录对象—>登录用户:SecurityUtils.getSubject(); ,***(User)subject.getPrincipal();***
- SimpleAuthorizationInfo里面有授权,授予角色的函数
- addRole(String role)
- setRoles(Set<String> set)
- addStringPermissions(String permission)
- addObjectPermissions(Set<Permission>)
- 最后对用户授权完毕后返回info
- 首先doGetAuthorizationInfo需要return一个AuthorizationInfo对象,我们先new一个他的实现类对象:SimpleAuthorizationInfo
-
doGetAuthenticationInfo(AuthenticationToken authenticationToken)
Authentication认证
- 首先doGetAuthenticationInfo需要return一个AuthenticationInfo对象,我们先new一个他的实现类对象:SimpleAuthenticationInfo
- 接着看这个实现类的构造函数
- 常用的前两个,传递给该构造函数一个用户principal,credentials登录凭证,一个realName当前登录的角色名,这个在下一层会封装进SimplePrincipalCollection集合里面
- 接着看这个实现类的构造函数
- 所以此时我们需要获取数据库的用户信息,将其交给SimpleAuthenticationInfo来进行验证
- 登录验证时,我们在Controller里对接收的用户名密码封装为shiro里面的UsernamePasswordToken令牌(此时并
未验证
),之后Realm就能在后面doGetAuthenticationInfon拿到登录的令牌 - doGetAuthenticationInfon拿到登录的令牌能获取用户名,我们就通过查询用户名获取密码进行验证
- 用户名不存在抛出UnknownAccountException异常
- 密码错误抛出IncorrectCredentialsException异常
- 我们就可以捕获异常进行相应的处理
- 首先doGetAuthenticationInfo需要return一个AuthenticationInfo对象,我们先new一个他的实现类对象:SimpleAuthenticationInfo
LoginController
@PostMapping("/LoginConfirm")
public String login(String username, String password, Model model) {
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//没有认证过
//封装用户的登录数据,获得令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//登录 及 异常处理
try {
//用户登录
subject.login(token);
Session subjectSession = subject.getSession();
model.addAttribute("loginUser",subjectSession.getAttribute("loginUser"));
return "redirect:index";
} catch (UnknownAccountException uae) {
log.warn("用户名不存在");
//如果用户名不存在
System.out.println("用户名不存在");
model.addAttribute("exception", "用户名不存在");
return "redirect:login";
} catch (IncorrectCredentialsException ice) {
log.warn("密码错误");
//如果密码错误
System.out.println("密码错误");
model.addAttribute("exception", "密码错误");
return "redirect:login";
}
}
UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("================执行了授权====================");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
// 拿到User对象
User currentUser = (User) subject.getPrincipal();
// 设置当前用户的权限
String permission = currentUser.getPermission();
System.out.println(currentUser.getUsername() + "的权限为 " + permission);
info.addStringPermission(permission);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("=====================执行了认证AuthenticationToken===================");
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
//连接真实的数据库
User user = userService.getUserByName(userToken.getUsername());
if(user == null){
//没有这个人
return null; //抛出异常 UnknownAccountException
}
// 登录成功 将用户信息存入session
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
// 密码认证,shiro做
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
三、shiro-thymeleaf
配置:
要想在前端使用shiro的标签要进行如下配置
在刚才的shiro配置类里新增如下Bean
@Configuration
public class ShiroConfig {
//thymeleaf集成shiro
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
在thymeleaf前端<html>标签里加入:
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
常用标签
<shiro:guest>
游客访问 <a href = "login.jsp"></a>
</shiro:guest>
user 标签:用户已经通过认证\记住我 登录后显示响应的内容
<shiro:user>
欢迎[<shiro:principal/>]登录 <a href = "logout">退出</a>
</shiro:user>
authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的
<shiro:authenticted>
用户[<shiro:principal/>] 已身份验证通过
</shiro:authenticted>
notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
<shiro:notAuthenticated>
未身份验证(包括"记住我")
</shiro:notAuthenticated>
principal 标签:显示用户身份信息,默认调用
Subjec.getPrincipal()获取,即Primary Principal
<shiro:principal property = "username"/>
hasRole标签:如果当前Subject有角色将显示body体内的内容
<shiro:hashRole name = "admin">
用户[<shiro:principal/>]拥有角色admin
</shiro:hashRole>
hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容
<shiro:hasAnyRoles name = "admin,user">
用户[<shiro:pricipal/>]拥有角色admin 或者 user
</shiro:hasAnyRoles>
lacksRole:如果当前 Subjec没有角色将显示body体内的内容
<shiro:lacksRole name = "admin">
用户[<shiro:pricipal/>]没有角色admin
</shiro:lacksRole>
hashPermission:如果当前Subject有权限将显示body体内容
<shiro:hashPermission name = "user:create">
用户[<shiro:pricipal/>] 拥有权限user:create
</shiro:hashPermission>
lacksPermission:如果当前Subject没有权限将显示body体内容
<shiro:lacksPermission name = "org:create">
用户[<shiro:pricipal/>] 没有权限org:create
</shiro:lacksPermission>
四、开发过程中遇到的问题
1、ShiroFilterFactoryBean配置了setUnauthorizedUrl不起作用
启发来源:https://blog.csdn.net/bicheng4769/article/details/86680955
问题重现:
在ShiroFilterFactoryBean中配置setUnauthorizedUrl,顾名思义未授权则跳转至对应请求路径
但是却发现无论怎么样,无权限的只会报错而不会跳到配置的那个请求
原因追溯:
我们先点进setUnauthorizedUrl看源码,发现这个url在applyUnauthorizedUrlIfNecessary里面使用
我们发现AuthorizationFilter这个过滤器使用了该url,点进去看实现类
发现AuthorizationFilter下的实现类只有Port,Premission,Role,Ssl这些过滤器;
而我在前面配置的FilterChainDefinitionMap里面是authc和anon类型的,不在该过滤器管理范围内,那么就会一直抛出异常org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method
解决:
手动捕获异常进行页面跳转
ShiroExceptionResolver实现HandlerExceptionResolver,当遇到该异常时手动跳转至位授权的页面
public class ShiroExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
System.out.println("==============异常开始=============");
//如果是shiro无权操作,因为shiro 在操作auno等一部分不进行转发至无权限url
if(ex instanceof UnauthorizedException){
ModelAndView mv = new ModelAndView("redirect:/noPermission");
return mv;
}
ex.printStackTrace();
System.out.println("==============异常结束=============");
ModelAndView mv = new ModelAndView("redirect:/err");
mv.addObject("exception", ex.toString().replaceAll("\n", "<br/>"));
return mv;
}
}
总结:
perms,roles,ssl,rest,port 属于AuthorizationFilter
anon,authcBasic,auchc,user 属于 AuthenticationFilter