整整搞了两天,网上好多文章没有标注出小版本,让我很是艰难。这里记录一下。
1:pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yunfei</groupId>
<artifactId>xxx</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>xxx</name>
<description>study demo</description>
<properties>
<java.version>1.8</java.version>
<mybatis-spring-boot>1.3.0</mybatis-spring-boot>
<mysql-connector>5.1.39</mysql-connector>
<fastjson.version>1.2.47</fastjson.version>
<ehcache.version>2.6.11</ehcache.version>
<ehcache-web.version>2.0.4</ehcache-web.version>
<commons-lang3.version>3.3.2</commons-lang3.version>
<commons-codec.version>1.9</commons-codec.version>
<shiro-spring.version>1.4.0</shiro-spring.version>
<shiro-redis.version>3.1.0</shiro-redis.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.7.0</version>
</dependency>
<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector}</version>
</dependency>
<!-- SpringBoot Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot}</version>
</dependency>
<!-- lombok依赖 可以减少大量的模块代码-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--Slf4j 依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<!-- logback 依赖 是slf4j的实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- Druid数据库连接池组件 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.18</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>${ehcache.version}</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-web</artifactId>
<version>${ehcache-web.version}</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<!--poi-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro-spring.version}</version>
</dependency>
<dependency>
<!--session持久化插件-->
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>${shiro-redis.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<!--允许移动生成的文件 -->
<verbose>true</verbose>
<!--允许覆盖生成的文件 -->
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
<include>**/*.yml</include>
<include>**/*.*</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.yml</include>
</includes>
</resource>
</resources>
</build>
</project>
2:shiro配置类
package com.yunfei.cultural.shiro;
import com.yunfei.cultural.filter.MyFormAuthenticationFilter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description: shiro配置类
* @Author: HuiYunfei
* @Date: 2019/11/9
*/
@Configuration
@Slf4j
@Data
@ConfigurationProperties(prefix = "spring.redis")
public class ShiroConfig {
private String host;
private int port = 6379;
private Duration timeout;
/**
* Filter工厂,设置对应的过滤条件和跳转条件
*
* @return ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//自定义过滤器,前后分离重定向会出现302等ajax跨域错误,这里直接返回错误不重定向
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("authc", new MyFormAuthenticationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
// 过滤器链定义映射
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/*
* anon:所有url都都可以匿名访问,authc:所有url都必须认证通过才可以访问;
* 过滤链定义,从上向下顺序执行,authc 应放在 anon 下面
* */
filterChainDefinitionMap.put("/system/login", "anon");
filterChainDefinitionMap.put("/file/*", "anon");
//filterChainDefinitionMap.put("/**", "corsAuthenticationFilter");
// 所有url都必须认证通过才可以访问
filterChainDefinitionMap.put("/**", "authc");
// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了, 位置放在 anon、authc下面
filterChainDefinitionMap.put("/system/logout", "logout");
// 未登录
//shiroFilterFactoryBean.setLoginUrl("/system/unLogin");
// 未授权
//shiroFilterFactoryBean.setUnauthorizedUrl("/system/unAuthorized");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis, 使用的是shiro-redis开源插件
*
* @return RedisSessionDAO
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setExpire(1800);
return redisSessionDAO;
}
/**
* Session ID 生成器
*
* @return JavaUuidSessionIdGenerator
*/
@Bean
public JavaUuidSessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}
/**
* 自定义sessionManager,禁用cookie,使用http header方式传入sessionId token
*
* @return SessionManager
*/
@Bean
public SessionManager sessionManager() {
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setSessionIdCookieEnabled(false);
mySessionManager.setSessionDAO(redisSessionDAO());
//这里修改sessionIdCookie的Name属性为jsid可以避免同一请求都会在redis生成一条新的sessionId记录
mySessionManager.getSessionIdCookie().setName("jsid");
return mySessionManager;
}
/**
* 配置shiro redisManager, 使用的是shiro-redis开源插件
*
* @return RedisManager
*/
private RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
//redisManager.setPort(port);
redisManager.setTimeout((int) timeout.toMillis());
return redisManager;
}
/**
* cacheManager 缓存 redis实现, 使用的是shiro-redis开源插件
*
* @return RedisCacheManager
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
// 必须要设置主键名称,shiro-redis 插件用过这个缓存用户信息
redisCacheManager.setPrincipalIdFieldName("id");
return redisCacheManager;
}
/**
* 权限管理,配置主要是Realm的管理认证
*
* @return SecurityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
return securityManager;
}
/**
* 自定义安全域,用户验证、权限等数据在此提供
* @return
*/
@Bean
public ShiroRealm myShiroRealm() {
ShiroRealm myShiroRealm = new ShiroRealm();
//关闭
myShiroRealm.setAuthenticationCachingEnabled(false);
//myShiroRealm.setAuthenticationCacheName("authenticcationCache");
myShiroRealm.setAuthorizationCachingEnabled(true);
myShiroRealm.setAuthorizationCacheName("authorizationCache");
return myShiroRealm;
}
/*
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public SimpleCookie cookie() {
// cookie的name,对应的默认是 JSESSIONID
SimpleCookie cookie = new SimpleCookie("SHARE_JSESSIONID");
cookie.setHttpOnly(true);
// path为 / 用于多个系统共享 JSESSIONID
//cookie.setPath("/");
return cookie;
}
/* 此项目使用 shiro 场景为前后端分离项目,这里先注释掉,统一异常处理已在 GlobalExceptionHand.java 中实现 */
}
这里要注意的是我在很多博客上看到说前后分离的时候shiro过滤器不能跳转jsp,要直接返回给客户端状态让客户端控制跳转,所以这里要shiroFilterFactoryBean.setLoginUrl("/system/unLogin");重定向一下,然后在controller里边返回给json给前端。但是!!!实际上这么操作会出现前端页面循环跳转跨域问题:request doesnt pass access control check:Redirect is not allowed for a preflight request。所以改成在上边添加自定义登陆校验异常过滤器MyFormAuthenticationFilter,然后设为"authc"。
在缓存了用户的认证、授权信息后shiro提供的退出方法有一个bug就是无法删除用户的认证信息,看过底层redis操作的源码可以发现认证和授权的删除方法并不太一样。有兴趣的可以去看看源码然后在登陆认证方法返回SimpleAuthenticationInfo对象的时候返回用户的id去做对应的修改。
package com.yunfei.cultural.filter;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created by hui.yunfei@qq.com on 2019/11/18
*/
@Slf4j
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
public MyFormAuthenticationFilter() {
super();
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
log.info("进入自定义shiro拦截器isAccessAllowed方法");
if(request instanceof HttpServletRequest){
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")){
log.info("进入自定义shiro拦截器isAccessAllowed方法:OPTIONS请求");
return true;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws Exception {
log.info("进入身份认证失败filter");
// HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// httpServletResponse.setStatus(200);
// httpServletResponse.setContentType("application/json;charset=utf-8");
// PrintWriter pw = httpServletResponse.getWriter();
// ResultObj result=new ResultObj();
// result.setInfo(401);
// result.setMsg("身份认证失败,请重新登录");
// pw.write(JSONObject.toJSONString(result));
// pw.flush();
// pw.close();
// return false;
WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
我这个地方直接printwriter打印的信息前端看不到不知道为啥,没办法只能把http状态码改成401校验错误给前端让他们判断是否校验成功。
理论上角色、权限认证失败也可以直接重写对应的过滤器RolesAuthorizationFilter、PermissionsAuthorizationFilter的onAccessDenied方法。我这么试过但是没有起作用,因为我的权限、角色认证失败被异常处理类捕捉了。
package com.yunfei.cultural.utils.exception;
import com.yunfei.cultural.utils.result.ResultObj;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import java.util.List;
/**
* @author http://gblfy.com
* @Description 全局异常处理
* @Date 2019/9/14 15:34
* @version1.0
*/
@EnableWebMvc
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHand {
/**
* 401 - 未登录
*/
@ExceptionHandler(UnLoginException.class)
public ResultObj handleUnLoginException(UnLoginException e) {
String msg = e.getMessage();
log.error("登录异常:", e);
ResultObj resultObj = new ResultObj();
resultObj.setInfo(401);
resultObj.setMsg(msg);
return resultObj;
}
/**
* 900 - 参数异常
*/
@ExceptionHandler(LogicException.class)
public ResultObj handleLogicException(LogicException e) {
String msg = e.getMessage();
log.error("参数异常", e);
ResultObj resultObj = new ResultObj();
resultObj.setInfo(900);
resultObj.setMsg(msg);
return resultObj;
}
/**
* 403 - 无权限
*/
@ExceptionHandler(UnauthorizedException.class)
public ResultObj handleLoginException(UnauthorizedException e) {
String msg = e.getMessage();
log.error("用户无权限:", e);
ResultObj resultObj = new ResultObj();
resultObj.setInfo(403);
resultObj.setMsg("用户无权限");
return resultObj;
}
/**
* 999 - 服务器异常
*/
@ExceptionHandler(SystemException.class)
public ResultObj handleSysException(SystemException e) {
String msg = "服务内部异常!" + e.getMessage();
log.error(msg, e);
ResultObj resultObj = new ResultObj();
resultObj.setInfo(999);
resultObj.setMsg(e.getMessage());
return resultObj;
}
/**
* 999 - 服务器异常
*/
@ExceptionHandler(Exception.class)
public ResultObj handleException(Exception e) {
String msg = "服务内部异常!" + e.getMessage();
log.error(msg, e);
ResultObj resultObj = new ResultObj();
resultObj.setInfo(999);
resultObj.setMsg(e.getMessage());
return resultObj;
}
/**
* 处理参数绑定异常,并拼接出错的参数异常信息。
* <p>
* 创建人:leigq <br>
* 创建时间:2017年10月16日 下午9:09:22 <br>
* <p>
* 修改人: <br>
* 修改时间: <br>
* 修改备注: <br>
* </p>
*
* @param result
*/
private String handleBindingResult(BindingResult result) {
if (result.hasErrors()) {
final List<FieldError> fieldErrors = result.getFieldErrors();
return fieldErrors.iterator().next().getDefaultMessage();
}
return null;
}
}
有人要问那为什么登陆认证异常全局异常没有捕捉到呢,捕捉到了不就也可以不用重写过滤器了吗?理论上是这样但可能我捕捉的异常非shiro内部的登陆异常也可能是其他原因反正我没有成功,有搞成功的小伙伴可以贴在下边哦。
3:shiro认证授权类
package com.yunfei.cultural.shiro;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunfei.cultural.entity.TUser;
import com.yunfei.cultural.mapper.TRolePermissionsMapper;
import com.yunfei.cultural.mapper.TUserRoleMapper;
import com.yunfei.cultural.model.vo.RolePermissionsModel;
import com.yunfei.cultural.model.vo.UserRoleModel;
import com.yunfei.cultural.service.UserService;
import com.yunfei.cultural.utils.MySimpleByteSource;
import com.yunfei.cultural.utils.ShiroUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
/**
* @Description: 自定义shiro认证赋权类
* @Author: HuiYunfei
* @Date: 2019/11/9
*/
@Slf4j
@Component
public class ShiroRealm extends AuthorizingRealm {
public ShiroRealm() {
}
@Autowired
@SuppressWarnings("all")
public ShiroRealm(UserService userService,TUserRoleMapper userRoleMapper,TRolePermissionsMapper rolePermissionsMapper) {
this.userService = userService;
this.rolePermissionsMapper=rolePermissionsMapper;
this.userRoleMapper=userRoleMapper;
}
@Resource
private UserService userService;
@Autowired
private TUserRoleMapper userRoleMapper;
@Autowired
private TRolePermissionsMapper rolePermissionsMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
TUser user = (TUser) principals.getPrimaryPrincipal();
//TUser user = userService.findUserByUserName(username);
//获取用户角色
List<UserRoleModel> userRoleList=userRoleMapper.findUserRoleByUserId(user.getId());
if(userRoleList.size()>0){
userRoleList.forEach(t->{
authorizationInfo.addRole(t.getRoleMarking());
});
}
//获取用户权限
List<RolePermissionsModel> rolePermissionsList = rolePermissionsMapper.findRolePermissionsByUserId(user.getId());
if(rolePermissionsList.size()>0){
rolePermissionsList.forEach(t->{
authorizationInfo.addStringPermission(t.getPermissionsMarking());
});
}
return authorizationInfo;
}
/*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
//获取用户的输入的账号.
String username = (String) token.getPrincipal();
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
TUser user = userService.findUserByUserName(username);
if(user==null){
throw new UnknownAccountException();
}
if(user.getStatus()==1){
throw new DisabledAccountException("账号已禁用!");
}
//处理session
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
DefaultWebSessionManager sessionManager = (DefaultWebSessionManager)securityManager.getSessionManager();
Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();//获取当前已登录的用户session列表
if(sessions.size()>0){
for(Session session:sessions){
//清除该用户以前登录时保存的session
if(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)!=null){
Object obj = ((SimplePrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)).asList().get(0);
ObjectMapper objectMapper = new ObjectMapper();
TUser tUser = objectMapper.convertValue(obj, TUser.class);
if(username.equals(tUser.getUsername())) {
sessionManager.getSessionDAO().delete(session);
}
}
}
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //用户名
user.getPassword(), //密码
//ByteSource.Util.bytes(user.getSalt()),// md5(salt+password),采用明文访问时,不需要此句
new MySimpleByteSource(user.getSalt()),
getName() //realm name
);
return authenticationInfo;
}
/**
* 将自己的验证方式加入容器
*
* 凭证匹配器(由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
*
* @param credentialsMatcher
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
/**
* 散列算法:这里可以使用MD5算法 也可以使用SHA-256
*/
hashedCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);
// 散列的次数,比如散列16次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);
super.setCredentialsMatcher(hashedCredentialsMatcher);
}
/**
* 重写方法,清除当前用户的的 授权缓存
* @param principals
*/
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
/**
* 重写方法,清除当前用户的 认证缓存
* @param principals
*/
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
/**
* 自定义方法:清除所有 授权缓存
*/
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
/**
* 自定义方法:清除所有 认证缓存
*/
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
/**
* 自定义方法:清除所有的 认证缓存 和 授权缓存
*/
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}
身份认证方法发现每次用户重新登陆以后之前的token并没有过期,所以加了一个处理session的功能。
4:自定义session获取类。因项目是前后分离的,前端是在Ajax的请求头加上token访问的,所以要重写这个取session的方法
package com.yunfei.cultural.shiro;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* @Description: 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用),
* 我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。
* 自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法
* @Author: HuiYunfei
* @Date: 2019/11/9
*/
@Slf4j
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "token";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果请求头中有 Authorization 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return null;//super.getSessionId(request, response);
}
}
//这个方法加不加我也没看出来区别
@Override
protected Session retrieveSession(SessionKey sessionKey){
Serializable sessionId = getSessionId(sessionKey);
ServletRequest request = null;
if(sessionKey instanceof WebSessionKey){
request = ((WebSessionKey)sessionKey).getServletRequest();
}
if(request != null && sessionId != null){
Session session = (Session) request.getAttribute(sessionId.toString());
if(session != null){
return session;
}
}
Session session = super.retrieveSession(sessionKey);
if(request != null && sessionId != null){
request.setAttribute(sessionId.toString(),session);
}
return session;
}
}
5:登陆退出方法
public LoginResult login(LoginParams params) {
LoginResult result = new LoginResult();
// 获取Subject实例对象,用户实例
Subject currentUser = SecurityUtils.getSubject();
// 将用户名和密码封装到UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(params.getUsername(), params.getPassword());
// 认证
try {
// 传到 MyShiroRealm 类中的方法进行认证
currentUser.login(token);
// 构建缓存用户信息返回给前端
TUser user = (TUser) currentUser.getPrincipals().getPrimaryPrincipal();
//TUser user = this.userMapper.findByUserName(username);
//校验当前用户是否有角色
List<UserRoleModel> userRoleList=userRoleMapper.findUserRoleByUserId(user.getId());
if(userRoleList.size()==0){
throw new LogicException("用户暂无角色,不能登录");
}
//校验当前用户是否有权限登录到后台(是否管理员角色)
boolean isAdmin=false;
for (UserRoleModel userRole : userRoleList) {
if(userRole.getRoleMarking().equals(CommonConstants.ROLE_ADMIN_MARKING)){
isAdmin=true;
}
}
result.setIsAdmin(isAdmin);
BeanUtils.copyProperties(user, result);
result.setToken(currentUser.getSession().getId().toString());
userMapper.updateByPrimaryKeySelective(TUser.builder().id(user.getId()).token(result.getToken()).build());
}catch (UnknownAccountException e) {
throw new LogicException("账号不存在!");
}catch (IncorrectCredentialsException e) {
throw new LogicException("密码错误!");
}
return result;
}
@Override
public void logout(JSONObject params) {
Subject subject = SecurityUtils.getSubject();
subject.logout();
}
6:shiroUtils
package com.yunfei.cultural.utils;
import com.yunfei.cultural.entity.TUser;
import com.yunfei.cultural.shiro.ShiroRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import sun.misc.BASE64Encoder;
import java.security.SecureRandom;
import java.util.Random;
/**
* Shiro工具类
*/
public class ShiroUtils {
/** 加密算法 */
public final static String hashAlgorithmName = "SHA-256";
/** 循环次数 */
public final static int hashIterations = 16;
public static String sha256(String password, String salt) {
return new SimpleHash(hashAlgorithmName, password, salt, hashIterations).toString();
}
// 获取一个测试账号 admin
public static void main(String[] args) {
// 3743a4c09a17e6f2829febd09ca54e627810001cf255ddcae9dabd288a949c4a
String salt=getNextSalt();
System.out.println("salt:"+salt);
System.out.println("password:"+sha256("yunfei",salt)) ;
}
public static String getNextSalt() {
Random RANDOM = new SecureRandom();
byte[] salt = new byte[16];
RANDOM.nextBytes(salt);
String str = new BASE64Encoder().encode(salt);
return str;
}
/**
* 获取会话
*/
public static Session getSession() {
return SecurityUtils.getSubject().getSession();
}
/**
* Subject:主体,代表了当前“用户”
*/
public static Subject getSubject() {
return SecurityUtils.getSubject();
}
/**
* 重新赋值权限(在比如:给一个角色临时添加一个权限,需要调用此方法刷新权限,否则还是没有刚赋值的权限)
* @param myRealm 自定义的realm
* @param username 用户名
*/
// public static void reloadAuthorizing(ShiroRealm myRealm, String userName){
// Subject subject = SecurityUtils.getSubject();
// String realmName = subject.getPrincipals().getRealmNames().iterator().next();
// //第一个参数为用户名,第二个参数为realmName,test想要操作权限的用户
// subject.runAs(new SimplePrincipalCollection(userName, subject.getPrincipals().getRealmNames().iterator().next()));
// myRealm.getAuthorizationCache().remove(subject.getPrincipals());
// subject.releaseRunAs();
// }
/**
* @Description:清除所有用户的权限信息(修改用户、修改角色时调用)
* @Author: HuiYunfei
* @Date: 2019/11/12
*/
public static void clearAllCachedAuthorizationInfo(){
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
ShiroRealm shiroRealm = (ShiroRealm) securityManager.getRealms().iterator().next();
shiroRealm.clearAllCachedAuthorizationInfo();
}
/**
* @Description:清除所有用户的认证缓存(暂未启用认证缓存)
* @Author: HuiYunfei
* @Date: 2019/11/12
*/
public static void clearAllCachedAuthenticationInfo(){
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
ShiroRealm shiroRealm = (ShiroRealm) securityManager.getRealms().iterator().next();
shiroRealm.clearAllCachedAuthorizationInfo();
}
public static TUser getUserEntity() {
return (TUser) SecurityUtils.getSubject().getPrincipal();
}
public static Integer getUserId() {
return getUserEntity().getId();
}
public static void setSessionAttribute(Object key, Object value) {
getSession().setAttribute(key, value);
}
public static Object getSessionAttribute(Object key) {
return getSession().getAttribute(key);
}
public static boolean isLogin() {
return SecurityUtils.getSubject().getPrincipal() != null;
}
public static void logout() {
SecurityUtils.getSubject().logout();
}
}
里边提供了获取加密密码方法和清楚认证授权缓存的方法。这样在修改用户、角色、权限相关信息的时候可以删除缓存实现直接刷新对应用户权限功能。(清除单个用户的方法没调成功清除所有的是可用的)
最后:
前后分离解决跨域问题,在主启动文件直接添加过滤器
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080
// ,以降低安全风险。。
config.addAllowedOrigin("*");
// 允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
// 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等
config.addAllowedMethod("*");
/*
* config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET");//
* 允许Get的请求方法 config.addAllowedMethod("PUT");
* config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE");
* config.addAllowedMethod("PATCH");
*/
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}