Shiro简介
Subject
- 即当前的操作的“用户”,该用户是一个抽象概念,由 SecurityManager 管理,所有 Subject 都绑定到 SecurityManager
SecurityManager
- 安全管理器,所以安全相关的交互都会经过 SecurityManager ,相当于springmvc中前端控制器DispatcherServlet;
Realm
- 域,SecurityManager从Realm获取安全数据(如用户、角色、权限)进行校验,可以把 Realm 看成 DataSource,即安全数据源
认证授权逻辑
- 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
- 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
整合springboot
依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
配置类
配置步骤
- 配置realm类
- 配置SecurityManager管理器类(这里使用的是DefaultWebSecurityManager),关联上面配置的Realm
- 配置ShiroFilterFactoryBean过滤工厂类,关联配置的SecurityManager、过滤器
创建配置类
- shiroFilterFactoryBean.setFilterChainDefinitionMap配置过滤规则实现页面拦截
- 过滤规则使用map集合装配,使用LinkedHashMap保证存取有序
- shiroFilterFactoryBean.setLoginUrl设置登陆跳转页面
package com.hd.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
@Configuration
public class ShiroConfig {
/**
* 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/*
* 添加shiro内置过滤器:
* anon:无需认证
* authc:必须认证才可以访问
* user:如果使用remember的功能才可以访问
* perms:该资源必须得到资源权限才可以访问
* roles:该资源必须得到角色权限才可以访问
*/
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/css/**","anon");
filterChainDefinitionMap.put("/js/**","anon");
filterChainDefinitionMap.put("/img/**","anon");
filterChainDefinitionMap.put("/register.html","anon");
filterChainDefinitionMap.put("/public/login.html","anon");
filterChainDefinitionMap.put("/userlogin","anon");
filterChainDefinitionMap.put("/error/**","anon");
filterChainDefinitionMap.put("/findOne","anon");
//设置授权
filterChainDefinitionMap.put("/user/**","roles[admin]");
filterChainDefinitionMap.put("/**","authc");
//修改跳转页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
//自定义授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth.html");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
* @param userRealm
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建realm
* @return 用户realm
*/
@Bean(name="userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
}
自定义realm
认证
- 继承AuthorizingRealm类,实现doGetAuthorizationInfo(授权)和doGetAuthenticationInfo(认证)方法
- Subject.login(token)登陆时,会跳转到认证授权的方法
package com.hd.config;
import com.hd.entity.Role;
import com.hd.entity.User;
import com.hd.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户名
String userName = (String)authenticationToken.getPrincipal();
// 查询有无该用户
User user = userService.findUserByName(userName);
if (null == user) {
// 没有该用户
return null; // 登陆时会抛出UnknownAccountException
}
/**
* 密码认证
* 参数1: 从数据库获取的userduix
* 参数2: 密码
* 参数3: 当前realm名称
*/
return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
}
}
授权
- SecurityUtils.getSubject().getPrincipal():获取认证时传递的用户对象
- SimpleAuthorizationInfo:授权信息对象
- SimpleAuthorizationInfo.addStringPermission():设置当前用户的权限
import com.hd.entity.Auth;
import com.hd.entity.Role;
import com.hd.entity.User;
import com.hd.service.RoleService;
import com.hd.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.List;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Autowired
RoleService roleService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 这里拿到的User对象是认证时设置的SimpleAuthenticationInfo类的参数
User user = (User) SecurityUtils.getSubject().getPrincipal();
// 授权信息对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 获取用户角色
Role role = userService.findRoleByName(user.getName());
//todo:role=null ??
// 将角色名称提供给info
HashSet<String> roles = new HashSet<>();
roles.add(role.getRole_name());
info.setRoles(roles);
// 获取角色所有权限,将权限名称提供给info
List<Auth> auths = roleService.findAuthByRoleId(role.getRole_id());
for (Auth auth :
auths) {
// 设置当前用户的权限
info.addStringPermission(auth.getAuth_name());
}
return info;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户名
String userName = (String)authenticationToken.getPrincipal();
// 查询有无该用户
User user = userService.findUserByName(userName);
if (null == user) {
// 没有该用户
return null; // 登陆时会抛出UnknownAccountException
}
/**
* 密码认证
* 参数1: 从数据库获取的userduix
* 参数2: 密码
* 参数3: 当前realm名称
*/
return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
}
}
登陆
/**
* 登录验证
*/
@PostMapping(value = "/userlogin")
public String user_login(String username, String password, Model model){
// 封装当前用户数据
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
// 获取当前用户
Subject currentUser = SecurityUtils.getSubject();
try {
// 执行登陆操作
// 主体提交登录请求到SecurityManager
currentUser.login(token);
}catch (IncorrectCredentialsException ice){
model.addAttribute("msg","密码不正确");
}catch(UnknownAccountException uae){
model.addAttribute("msg","账号不存在");
}catch(AuthenticationException ae){
model.addAttribute("msg","状态不正常");
}
if(currentUser.isAuthenticated()){
return Result.ok("auth success");
}else{
token.clear();
return Result.build(100001, "auth fail");
}
}
Shiro 内置过滤器及实现类
Filter Name | Class |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter |
noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
自定义Shiro Filter
- 继承shiro过滤器(AccessControlFilter),重写onAccessDenied、isAccessAllowed
- isAccessAllowed:判断是否登录,在登录的情况下会走此方法,此方法返回true直接访问控制器
- onAccessDenied:是否是拒绝登录,没有登录的情况下会走此方法,此方法返回true直接访问控制器
public class UserFilter extends AccessControlFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//获取用户
User sessionUser = (User) request.getSession().getAttribute("user");
if (sessionUser != null) {
//已经登陆,放行
return true;
} else {
//在Cookie中获取token
String loginToken = CookieUtil.findCookieByName(request, "loginToken");
if (StringUtils.isNotBlank(loginToken)) {
//根据token找用户
UserService userService = (UserService)SpringContextUtil.getBean(UserService.class);
User userByToken = userService.getUserByToken(loginToken);
if (userByToken != null) {
//有对应token的用户,保存到session,放行
request.getSession().setAttribute("user", userByToken);
return true;
} else {
//没有对应用户,清除Cookie
CookieUtil.clearCookie(request, response, "loginToken");
return false;
}
} else {
//没有cookie,也没有登陆。是index请求获取用户信息,可以放行
if (request.getRequestURI().contains("session")) {
return true;
}
//没有cookie凭证,跳转到登陆页
response.sendRedirect("/login.html");
return false;
}
}
}
}
filter、realm中使用SpringBean
shiro中自定义的filter和realm中并没有被注入到Spring容器中,如果想要在filter中使用SpringBean,可以使用设置属性的方式
public class PasswordFilter extends AccessControlFilter{
// 自定义的Filter中StringRedisTemplate来作为缓存的操作,此属性需要在Spring中获取
private StringRedisTemplate redisTemplate;
...
}
**用已经注入到容器的类,去设置过滤器属性,即将stringRedisTemplate赋值给PasswordFilter **
@Component
public class ShiroFilterChainManager {
private final StringRedisTemplate stringRedisTemplate;
@Autowired
public ShiroFilterChainManager(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public Map<String, Filter> initFilterMap() {
Map<String, Filter> filterMap = new LinkedHashMap<>();
PasswordFilter passwordFilter = new PasswordFilter();
passwordFilter.setRedisTemplate(stringRedisTemplate);
filterMap.put("auth", passwordFilter);
filterMap.put("jwt", jwtFilter);
return filterMap;
}
}
shiro配置类
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, ShiroFilterChainManager shiroFilterChainManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置过滤器链
shiroFilterFactoryBean.setFilters(shiroFilterChainManager.initFilterMap());
...
}