首先确保在不进行shiro整合时正常登录以及拥有权限,然后我们再开始,以下是我个人的思路。
首先,准备依赖(这里只讲关键依赖,目前我就用到了这些,够用就行,也别把相关依赖全部弄进来):
<!--shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<!--thymeleaf自动渲染-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
我的有关shiro整合的结构图:
主要了解的类是MyRealm和MyShiroConfig,在配置的时候参考了jiankang66的springboot整合shiro实现权限控制这篇(链接地址:https://blog.csdn.net/jiankang66/article/details/90473517)
首先MyShiroConfig的配置:
package team.glh.springboot.plant_manager.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import team.glh.springboot.plant_manager.shiro.CustomSessionManager;
import team.glh.springboot.plant_manager.shiro.MyMatcher;
import team.glh.springboot.plant_manager.shiro.MyRealm;
import team.glh.springboot.plant_manager.shiro.MyRememberFilter;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class MyShiroConfig {
//密码验证器
@Bean("credentialsMatcher")
public CredentialsMatcher credentialsMatcher() {
return new MyMatcher();
}
//权限验证器
@Bean("myRealm")
public MyRealm myRealm(@Qualifier("credentialsMatcher") CredentialsMatcher credentialsMatcher) {
MyRealm myRealm = new MyRealm();
//给权限验证器配置上自定义的密码验证器
myRealm.setCredentialsMatcher(credentialsMatcher);
return myRealm;
}
@Bean
public CacheManager cacheManager(){
return new MemoryConstrainedCacheManager();
}
/**
* cookie对象;
* rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cookie的有效时间等等。
*
* @return
*/
@Bean
public SimpleCookie rememberMeCookie() {
// 这个参数是cookie的名称,对应前端的checkbox的name=rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
// cookie生效时间为10秒
simpleCookie.setMaxAge(10);
return simpleCookie;
}
/**
* cookie管理对象;
* rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
*
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
@Bean
public MyRememberFilter MyRememberFilter(){
return new MyRememberFilter();
}
/**
* 自定义sessionManager
* @return
*/
@Bean
public SessionManager sessionManager(){
return new CustomSessionManager();
}
//桥梁,主要是Realm的管理认证配置
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//注入自定义myRealm
defaultWebSecurityManager.setRealm(myRealm);
//注入自定义cacheManager
defaultWebSecurityManager.setCacheManager(cacheManager());
//注入记住我管理器
defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
//注入自定义sessionManager
defaultWebSecurityManager.setSessionManager(sessionManager());
//自定义缓存实现,使用redis
// defaultWebSecurityManager.setSessionManager(SessionManager());
return defaultWebSecurityManager;
}
//进行全局配置,Filter工厂,设置对应的过滤条件和跳转条件
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
//shiro对象
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl("/toLogin");
bean.setSuccessUrl("/success");
Map<String, Filter> filterMap=new LinkedHashMap<String,Filter>();
filterMap.put("MyRememberFilter",MyRememberFilter());
/* //自定义拦截器
Map<String, Filter> filterMap=new LinkedHashMap<String,Filter>();
//限制同一账号同时在线的个数
// filterMap.put("kickout",kickoutSessionControlFilter());
bean.setFilters(filterMap);*/
//MAP
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<String, String>();
/*
认证顺序是从上往下执行。
*/
linkedHashMap.put("/logout", "logout");//在这儿配置登出地址,不需要专门去写控制器。
linkedHashMap.put("/static/**", "anon");
//开启注册页面不需要权限
// linkedHashMap.put("/register", "anon");
// linkedHashMap.put("/saveregister", "anon");
//验证phone唯一
// linkedHashMap.put("/solephone", "anon");
//获取验证码
// linkedHashMap.put("/getcode", "anon");
//验证码判断
// linkedHashMap.put("/comparecode", "anon");
// linkedHashMap.put("/websocket", "anon");//必须开启。
// linkedHashMap.put("/css/**", "anon");//不需要验证
// linkedHashMap.put("/js/**", "anon");//不需要验证
//配置错误页面
linkedHashMap.put("error", "anon");//不需要验证
linkedHashMap.put("/avatars/**", "anon");//不需要验证
linkedHashMap.put("/css/**", "anon");//不需要验证
linkedHashMap.put("/font/**", "anon");//不需要验证
linkedHashMap.put("/images/**", "anon");//不需要验证
linkedHashMap.put("/js/**", "anon");//不需要验证
linkedHashMap.put("/login/**", "anon");//不需要验证
linkedHashMap.put("/**", "user");//需要进行权限验
bean.setFilterChainDefinitionMap(linkedHashMap);
return bean;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
//加入·注解的使用,不加入这个注解不生效
//启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
// * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();
sourceAdvisor.setSecurityManager(securityManager);
return sourceAdvisor;
}
// 进行权限认证的,没有会使得前台的shiro标签无法使用
//shiro结合thymeleaf实现细粒度权限控制
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
MyRealm
package team.glh.springboot.plant_manager.shiro;
import com.alibaba.fastjson.JSON;
import org.apache.shiro.authc.*;
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.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import team.glh.springboot.plant_manager.entity.sys.SysPermission;
import team.glh.springboot.plant_manager.entity.sys.SysRole;
import team.glh.springboot.plant_manager.entity.sys.SysUser;
import team.glh.springboot.plant_manager.service.itf.sys.ISysPermissionService;
import team.glh.springboot.plant_manager.service.itf.sys.ISysRoleService;
import team.glh.springboot.plant_manager.service.itf.sys.ISysUserService;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MyRealm extends AuthorizingRealm {
private Logger logger= LoggerFactory.getLogger(this.getClass());
@Autowired
private ISysUserService sysUserService;
@Autowired
private ISysRoleService roleService;
@Autowired
private ISysPermissionService sysPermissionService;
/**
* 权限验证
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
SysUser user = sysUserService.listByAccount(principals.toString());
if (null != user) {
SysRole role = roleService.getById(user.getRoleId());
if (null != role) {
List<SysPermission> permissionList = sysPermissionService.listByRoleId(role.getId());
logger.info("当前用户拥有的权限:"+JSON.toJSONString(permissionList));
Set<String> roles = new HashSet<>();
Set<String> permissions = new HashSet<>();
roles.add(role.getCode());
if (null != permissionList) {
for (SysPermission permission : permissionList) {
permissions.add(permission.getCode());
}
}
logger.info("拥有的角色:"+role.getCode()+"拥有的权限"+ JSON.toJSONString(permissions));
simpleAuthorizationInfo.addRoles(roles);
simpleAuthorizationInfo.addStringPermissions(permissions);
}
simpleAuthorizationInfo.addStringPermission("user");
}
return simpleAuthorizationInfo;
}
/**
* 用户身份验证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从token获取用户名,从主体传过来的认证信息中获取
//加这一步的目的是在post请求时会先进入认证然后再到请求。
if(authenticationToken.getPrincipal()==null){
return null;
}
//获取用户的登录信息,用户名
String account=authenticationToken.getPrincipal().toString();
//根据service调用用户名,查找用户的全部信息
//通过用户名到数据库获取凭证
SysUser sysUser = sysUserService.listByAccount(account);
if (null != sysUser) {
String password = sysUser.getPassword();
Integer state = sysUser.getState();
// 1:正常 2:禁用 3:锁定
if (state == (short)2) {
throw new DisabledAccountException("禁用账号");
} else if (state == (short)3) {
throw new LockedAccountException("密码输入错误次数大于5,账号锁定");
} else {
return new SimpleAuthenticationInfo(account, password,getName());
}
} else {
throw new UnknownAccountException("账户不存在");
}
}
}
CustomSessionManager
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
public class CustomSessionManager extends DefaultWebSessionManager {
}
MyMatcher
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.crypto.hash.Sha384Hash;
/**
* @Author:
* @Date: Created in 下午5:32 2020/2/11
* @todo:验证密码 查找到了该用户 自定义密码验证器
*/
public class MyMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String pwd = encrypt(String.valueOf(usernamePasswordToken.getPassword()));
String mysqlpwd = (String) info.getCredentials();
return this.equals(pwd, mysqlpwd);
}
//将传进来的密码进行加密的方法
private String encrypt(String data){
String sha384Hex=new Sha384Hash(data).toBase64();
return sha384Hex;
}
}
MyRemenberFilter
package team.glh.springboot.plant_manager.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Author: Gan LingHao
* @Date: Created in 下午6:32 2020/2/11
* @todo: 暂时未启用
*/
public class MyRememberFilter extends FormAuthenticationFilter {
protected boolean isAccessAllowed(HttpServletRequest request, HttpServletResponse response, Object mappedValue){
Subject subject=getSubject(request,response);
if(!subject.isAuthenticated() && subject.isRemembered()){
if(subject.getSession().getAttribute("user")==null &&subject.getPrincipal()!=null){
subject.getSession().setAttribute("user",subject.getPrincipal());
}
}
return subject.isAuthenticated() || subject.isRemembered();
}
}
ShiroUtil
package team.glh.springboot.plant_manager.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import team.glh.springboot.plant_manager.entity.sys.SysUser;
import team.glh.springboot.plant_manager.vo.Contants;
import team.glh.springboot.plant_manager.vo.SysPermissionVO;
import java.util.List;
import java.util.ResourceBundle;
/**
* @Author:
* @Date: Created in 下午10:47 2019/12/30
* @todo: 加密密码
*/
public class ShiroUtil {
private static Logger logger= LoggerFactory.getLogger(ShiroUtil.class);
private static String TYPE;
private static Integer COUNT;
static {
ResourceBundle resourceBundle=ResourceBundle.getBundle("realm");
TYPE=resourceBundle.getString("realm.hashed.type");
COUNT= Integer.parseInt(resourceBundle.getString("realm.hashed.count"));
}
/**
* 密码加密
* @param account
* @param password
* @return
*/
public static String saltPwd(String account,String password){
ByteSource salt= ByteSource.Util.bytes(account);
return new SimpleHash(TYPE,password,salt,COUNT).toString();
}
/**
* 当前操作用户保存到session
* @param user
*/
public static void setCurrentUser(SysUser user){
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute(Contants.LOGIN_USER,user);
}
/**
* 获取当前登录成功的用户
* @return
*/
public static SysUser getCurrentLoginUser(){
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
SysUser user = null != session.getAttribute(
Contants.LOGIN_USER)?(SysUser)session.getAttribute(Contants.LOGIN_USER):null;
return user;
}
/**
* 当用户退出系统或登录失败时移除当前操作用户
*/
public static void removeCurrentUser(){
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.removeAttribute(Contants.LOGIN_USER);
logger.info("当前sessionId "+session.getId());
}
/**
* 将获取到的有效权限放到session中
* @param allPermission
*/
public static void setAllPermission(List<SysPermissionVO> allPermission){
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute(Contants.ALL_PERMISSION,allPermission);
session.touch();
}
/**
* 移除之前session中获取到的有效权限
*/
public static void removeAllPermission(){
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.removeAttribute(Contants.ALL_PERMISSION);
}
/**
* 重新赋值权限(在比如:给一个角色临时添加一个权限,需要调用此方法刷新权限,否则还是没有刚赋值的权限)
* @param account 当前登录用户的用户名
*/
public static void reloadAuthorizing(String account){
//重新修改权限后清楚缓存,调用doGetAuthorizationInfo重新取角色的权限信息
RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
MyRealm myRealm = (MyRealm) rsm.getRealms().iterator().next();
Subject subject = SecurityUtils.getSubject();
String realmName = subject.getPrincipals().getRealmNames().iterator().next();
//shiroRealm.clearAllCachedAuthorizationInfo2();//清楚所有用户权限
//第一个参数为用户名,第二个参数为realmName,test想要操作权限的用户
SimplePrincipalCollection principals = new SimplePrincipalCollection(account,realmName);
subject.runAs(principals);
myRealm.getAuthorizationCache().remove(subject.getPrincipals());
subject.releaseRunAs();
}
}
LoginController
package team.glh.springboot.plant_manager.controller.basic;
import org.apache.ibatis.annotations.Param;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import team.glh.springboot.plant_manager.entity.sys.SysUser;
import team.glh.springboot.plant_manager.service.itf.sys.ISysPermissionService;
import team.glh.springboot.plant_manager.service.itf.sys.ISysRoleService;
import team.glh.springboot.plant_manager.service.itf.sys.ISysUserService;
import team.glh.springboot.plant_manager.shiro.ShiroUtil;
import team.glh.springboot.plant_manager.vo.ResponseMessage;
import team.glh.springboot.plant_manager.vo.SysPermissionVO;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.*;
/**
* @Author:
* @Date: Created in 下午12:32 2020/2/8
* @todo: 登录
*/
@Controller
public class LoginController {
private Logger logger= LoggerFactory.getLogger(getClass());
@Autowired
private ISysUserService sysUserService;
@Autowired
private ISysRoleService roleService;
@Autowired
private ISysPermissionService sysPermissionService;
@RequestMapping("/toLogin")
public String toLogin(){
return "basic/login";
}
/**
* post登录
*
* @param account
* @param password
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login2(@Param("account") String account, @Param("password") String password,Model model,HttpServletRequest request) {
Subject subject= SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(account,password);
ResponseMessage msg=new ResponseMessage();
try{
subject.login(usernamePasswordToken);
if(subject.isAuthenticated()){
logger.info("登录成功");
String host=request.getRemoteHost()+":"+request.getRemotePort();
logger.info("当前登录host:port"+host);
SysUser sysUser=sysUserService.listByAccount(account);
sysUser.setLastLoginTime(sysUser.getThisLoginTime());
sysUser.setLastLoginIp(sysUser.getThisLoginIp());
sysUser.setThisLoginTime(LocalDateTime.now());
sysUser.setThisLoginIp(host);
//将用户信息存入session中
ShiroUtil.setCurrentUser(sysUser);
sysUserService.saveOrUpdate(sysUser);
return "redirect:success";
}else{
msg.setValue(110);
msg.setDesc("认证失败");
logger.info("认证失败");
model.addAttribute("msg",msg);
return "basic/login";
}
}catch (UnknownAccountException ex){
msg.setValue(110);
msg.setDesc("账号不存在");
logger.info("账号不存在");
model.addAttribute("msg",msg);
return "basic/login";
} catch (IncorrectCredentialsException ex){
msg.setValue(110);
msg.setDesc("密码错误");
logger.info("密码错误");
model.addAttribute("msg",msg);
return "basic/login";
} catch (LockedAccountException ex){
msg.setValue(110);
msg.setDesc("错误次数过多,锁定账号");
logger.info("错误次数过多,锁定账号");
model.addAttribute("msg",msg);
return "basic/login";
} catch (DisabledAccountException ex){
msg.setValue(110);
msg.setDesc("此帐号已被禁用");
logger.info("此帐号已被禁用");
model.addAttribute("msg",msg);
return "basic/login";
}
}
@RequestMapping("/success")
public String success(Model model){
List<SysPermissionVO> permissions=sysPermissionService.listByEffective(1,1);
ShiroUtil.setAllPermission(permissions);
model.addAttribute("index","index");
return "basic/index";
}
@RequestMapping("/logout")
public String logout(){
logger.info(ShiroUtil.getCurrentLoginUser().getName()+"退出系统");
// SecurityUtils.getSubject().logout();
ShiroUtil.removeCurrentUser();
ShiroUtil.removeAllPermission();
return "basic/login";
}
}
前端关于shiro的代码
<li
th:each="permission:${session.ALL_PERMISSION}" class=""
th:class="${permission.code == parentCode}?'active open'"
>
<!-- <shiro:hasPermission th:name="${permission.code}">-->
<a href="#" class="dropdown-toggle" shiro:hasPermission="${permission.code}">
<i class="icon-list"></i>
<span class="menu-text"> [[${permission.name}]]</span>
<b class="arrow icon-angle-down"></b>
</a>
<ul class="submenu">
<li th:each="children:${permission.childrens}"
th:class="${children.code == sonCode}?'active'"
>
<!-- <shiro:hasPermission th:name="${children.code}">-->
<a href="tables.html" th:href="@{${children.url}}" shiro:hasPermission="${children.code}">
<i class="icon-double-angle-right"></i>
[[${children.name}]]
</a>
<!-- </shiro:hasPermission>-->
</li>
</ul>
<!-- </shiro:hasPermission>-->
</li>
要想开启提示,导入它的命名空间
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
这就是有关整合的全部有效代码,貌似不缺。