spring boot + shiro + redis 整合(完整)
什么是shiro
shiro是一个Java平台的开源权限框架,用于认证和访问授权。具体来说,满足对如下元素的支持:
- 用户,角色,权限(仅仅是操作权限,数据权限必须与业务需求紧密结合),资源(url)
- 用户分配角色,角色定义权限
- 访问授权时支持角色或者权限,并且支持多级的权限定义
1.数据库设计
数据库中有分别有6个表,分别是:权限表(permissions)、用户表(users)、用户权限关系表(user_role)、角色表(roles)、角色权限关系表(role_permission)、shiro过滤器表( resource)
数据库下载地址: https://pan.baidu.com/s/1AMKwNfRimPPzqWHY30S1HQ 提取码: r9oh
2.创建springboot项目并在pom加入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 导入web支持:SpringMVC开发支持,Servlet相关的程序 -->
<!-- web支持,SpringMVC, Servlet支持等 web 启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入thymeleaf依赖-->
<!--thymeleaf启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- jasper -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- fastjson阿里巴巴jSON处理器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- reids -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- shiro+redis缓存插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.6</version>
</dependency>
<!-- mysql 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
<!-- thymeleaf整合shiro标签 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
3. 编辑application.yml
#springboot
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/shiro?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
redis:
host: localhost
port: 6379
jedis:
pool:
max-idle: 8
min-idle: 0
max-active: 8
max-wait: -1
timeout: 0
mybatis:
mapper-locations: classpath:mapper/*.xml #*/
type-aliases-package: com.jbl.springboot_redis_shiro.entity
4.建包搭架子(先把包建完)
4.1创建实体类
/**
* @Author: yfj
* @Description: 资源
* Serializable 要缓存的JavaBean必须实现Serializable接口,因为Spring会将对象先序列化再存入 Redis
*/
@Data
public class Resources implements Serializable {
public Integer id;
public String key;
public String val;
public Integer order;
}
/**
* @Author: yfj
* @Description: 用户信息
* Serializable 要缓存的JavaBean必须实现Serializable接口,因为Spring会将对象先序列化再存入 Redis
*/
@Data
public class User implements Serializable {
public Integer u_id;
public String u_name;
public String u_pwd;
}
我用的lombok插件,简化get、set、ToString等方法想学习的可以点击下面链接:
lombok基本使用和安装
4.2springboot启动类
@SpringBootApplication
@MapperScan("com.jbl.springboot_redis_shiro.dao") //这里填的是自己dao层接口的路径
public class SpringbootRedisShiroApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedisShiroApplication.class, args);
}
}
4.3启动springboot自动跳转到登陆页面
/**
* 启动项目自动跳转到登陆页面
*/
@SpringBootConfiguration
public class AutoStartProjectInDefaultBrowser implements CommandLineRunner {
@Value("${server.port}")
private String port;
@Override
public void run(String ... args) throws Exception {
try {
Runtime.getRuntime().exec("cmd /c start http://localhost:" + port + "/shiro/login");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
4.4全局异常类
/**
* @author yfj
* @version 1.0
* @date 2020/04/16 下午 03:11
* @description: 全局异常类
*/
@ControllerAdvice
public class CtrlExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(CtrlExceptionHandler.class);
//拦截未授权页面
@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler(UnauthorizedException.class)
public String handleException(UnauthorizedException e) {
logger.debug(e.getMessage());
return "403";
}
@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler(AuthorizationException.class)
public String handleException2(AuthorizationException e) {
logger.debug(e.getMessage());
return "403";
}
}
5.后台代码
5.1 Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jbl.springboot_redis_shiro.dao.ShiroMapper">
<select id="login" resultType="User" parameterType="String">
SELECT `u_id`,`u_name`,`u_pwd` FROM `users`
WHERE `u_name`= #{name} and `u_pwd` = #{u_pwd}
</select>
<select id="seRoles" resultType="String" parameterType="String">
SELECT r.`r_name` FROM `user_role` ur
LEFT JOIN `users` u ON(ur.`u_id`=u.`u_id`)
LEFT JOIN `roles` r ON(ur.`r_id`=r.`r_id`)
WHERE u.`u_name`=#{userName}
</select>
<select id="orders" resultType="Resources">
SELECT * FROM `resource` ORDER BY `orders`
</select>
<select id="quanxian" resultType="String" parameterType="String">
SELECT p.`p_name` FROM `role_permission` rp
LEFT JOIN `permissions` p ON(rp.`p_id`=p.`p_id`)
LEFT JOIN `roles` r ON(rp.`r_id`=r.`r_id`)
WHERE r.`r_name`IN(SELECT r.`r_name` FROM `user_role` ur
LEFT JOIN `users` u ON(ur.`u_id`=u.`u_id`)
LEFT JOIN `roles` r ON(ur.`r_id`=r.`r_id`)
WHERE u.`u_name`=#{userName})
</select>
</mapper>
5.2 Mapper接口
import com.jbl.springboot_redis_shiro.entity.Resources;
import com.jbl.springboot_redis_shiro.entity.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Set;
/**
* @author yfj
* @version 1.0
* @date 2020/04/15 下午 02:40
* @description:
*/
@Repository
public interface ShiroMapper {
/**
* 登陆
* @param name 用户名
* @param u_pwd 密码
* @return
*/
User login(@Param("name")String name,@Param("u_pwd")String u_pwd);
/**
* 查询资源
* @return List
*/
List<Resources> orders();
/**
* 根据用户查询角色
* @param userName
* @return
*/
Set<String> seRoles(@Param("userName")String userName);
/**
* 根据名字查询权限
* @param userName
* @return
*/
Set<String> quanxian(@Param("userName")String userName);
}
5.3 server接口
import com.jbl.springboot_redis_shiro.entity.Resources;
import com.jbl.springboot_redis_shiro.entity.User;
import java.util.List;
import java.util.Set;
/**
* @author yfj
* @version 1.0
* @date 2020/04/15 下午 02:42
* @description:
*/
public interface ShiroServer {
/**
* 登陆
* @return
*/
User login(String name,String u_Pwd);
/**
* 查询资源
* @return List
*/
List<Resources> orders();
/**
* 根据用户查询角色
* @param userName
* @return
*/
Set<String> seRoles(String userName);
/**
* 根据名字查询权限
* @param userName
* @return
*/
Set<String> quanxian(String userName);
}
server实现类
import com.jbl.springboot_redis_shiro.dao.ShiroMapper;
import com.jbl.springboot_redis_shiro.entity.Resources;
import com.jbl.springboot_redis_shiro.entity.User;
import com.jbl.springboot_redis_shiro.server.ShiroServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
/**
* @author yfj
* @version 1.0
* @date 2020/04/15 下午 02:43
* @description:
*/
@Service("ShiroServerImpl")
public class ShiroServerImpl implements ShiroServer{
@Autowired
private ShiroMapper shiroMapper;
@Override
public User login(String name, String u_Pwd) {
return shiroMapper.login(name,u_Pwd);
}
@Override
public List<Resources> orders() {
return shiroMapper.orders();
}
@Override
public Set<String> seRoles(String userName) {
return shiroMapper.seRoles(userName);
}
@Override
public Set<String> quanxian(String userName) {
return shiroMapper.quanxian(userName);
}
}
6.创建ShiroConfig类
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.jbl.springboot_redis_shiro.server.ShiroServer;
import org.apache.shiro.mgt.SecurityManager;
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.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author yfj
* @version 1.0
* @date 2020/04/15 下午 07:06
* @description: shiro配置
*/
@Configuration
public class ShiroConfig {
@Autowired
private ShiroServer shiroServer;
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 没有登陆的用户只能访问登陆页面
shiroFilterFactoryBean.setLoginUrl("/shiro/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/shiro/index");
// 未授权界面; ----这个配置了没卵用,具体原因想深入了解的可以自行百度
shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/wuquan");
//自定义拦截器
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
//限制同一帐号同时在线的个数。
filtersMap.put("kickout", kickoutSessionControlFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
// 权限控制map.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
/*
我加了下面这段代码启动就报错,如果有人知道是为什么请联系我,谢谢
List<Resources> list=shiroServer.orders();
for(Resources itms:list){
filterChainDefinitionMap.put(itms.getKey(),itms.getVal());
}*/
//添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
filterChainDefinitionMap.put("/shiro/kickout", "anon");
filterChainDefinitionMap.put("/shiro/login", "anon");
filterChainDefinitionMap.put("/shiro/submitLogin", "anon");
filterChainDefinitionMap.put("/shiro/index", "authc");
filterChainDefinitionMap.put("/shiro/tianjia", "perms[insert]");
filterChainDefinitionMap.put("/shiro/update", "perms[update]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost("localhost");
redisManager.setPort(6379);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(0);
// redisManager.setPassword(password);
return redisManager;
}
/**
* Session Manager
* 使用的是shiro-redis开源插件
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* 限制同一账号登录同时登录人数控制
*
* @return
*/
@Bean
public KickoutSessionControlFilter kickoutSessionControlFilter() {
KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
kickoutSessionControlFilter.setCacheManager(cacheManager());
kickoutSessionControlFilter.setSessionManager(sessionManager());
kickoutSessionControlFilter.setKickoutAfter(false);
kickoutSessionControlFilter.setMaxSession(1);
kickoutSessionControlFilter.setKickoutUrl("/shiro/kickout");
return kickoutSessionControlFilter;
}
/***
* 授权所用配置
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/***
* 使授权注解起作用不如不想配置可以在pom文件中加入
* <dependency>
*<groupId>org.springframework.boot</groupId>
*<artifactId>spring-boot-starter-aop</artifactId>
*</dependency>
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命周期处理器
*
*/
@Bean
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 配置ShiroDialect,用于thymeleaf和shiro标签配合使用
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
7.Realm(凭证认证、权限获取)
package com.jbl.springboot_redis_shiro.shiroconfig;
import com.jbl.springboot_redis_shiro.entity.User;
import com.jbl.springboot_redis_shiro.server.ShiroServer;
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.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
/**
* @author yfj
* @version 1.0
* @date 2020/04/15 下午 07:16
* @description:
*/
public class MyShiroRealm extends AuthorizingRealm {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
private ShiroServer shiroServer;
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
logger.info("---------------- 执行 Shiro 凭证认证 ----------------------");
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String name = token.getUsername();
String password = String.valueOf(token.getPassword());
// 从数据库获取对应用户名密码的用户
User userList = shiroServer.login(name,password);
if (userList == null) { //用户名不存在
return null; // shiro底层会抛出UnKnoAccountException 异常
}
logger.info("---------------- Shiro 凭证认证成功 ----------------------");
//判断密码
/**
* 第一个参数是否返回给controller里的login数据
* 第二个参数是数据库的密码
* 第三个参数是名字
*/
return new SimpleAuthenticationInfo(userList,userList.getU_pwd(),"");
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("---------------- 执行 Shiro 权限获取 ---------------------");
Object principal = principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
if (principal instanceof User) {
User userLogin = (User) principal;
//根据名字查询角色
Set<String> roles = shiroServer.seRoles(userLogin.getU_name());
authorizationInfo.addRoles(roles);
//根据名字查询权限
Set<String> permissions = shiroServer.quanxian(userLogin.getU_name());
authorizationInfo.addStringPermissions(permissions);
}
logger.info("---- 获取到以下权限 ----");
logger.info(authorizationInfo.getStringPermissions().toString());
logger.info("---------------- Shiro 权限获取成功 ----------------------");
return authorizationInfo;
}
}
8.限制并发人数登陆
package com.jbl.springboot_redis_shiro.shiroconfig;
import com.alibaba.fastjson.JSON;
import com.jbl.springboot_redis_shiro.entity.User;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
/**
* @author yfj
* @version 1.0
* @date 2020/04/15 下午 07:17
* @description: 限制并发人数登陆
*/
public class KickoutSessionControlFilter extends AccessControlFilter {
private String kickoutUrl; //踢出后到的地址
private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
private int maxSession = 1; //同一个帐号最大会话数 默认1
private SessionManager sessionManager;
private Cache<String, Deque<Serializable>> cache;
public void setKickoutUrl(String kickoutUrl) {
this.kickoutUrl = kickoutUrl;
}
public void setKickoutAfter(boolean kickoutAfter) {
this.kickoutAfter = kickoutAfter;
}
public void setMaxSession(int maxSession) {
this.maxSession = maxSession;
}
public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
//设置Cache的key的前缀
public void setCacheManager(CacheManager cacheManager) {
this.cache = cacheManager.getCache("shiro_redis_cache");
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
if(!subject.isAuthenticated() && !subject.isRemembered()) {
//如果没有登录,直接进行之后的流程
return true;
}
Session session = subject.getSession();
User user = (User) subject.getPrincipal();
String username = user.getU_name();
Serializable sessionId = session.getId();
//读取缓存 没有就存入
Deque<Serializable> deque = cache.get(username);
//如果此用户没有session队列,也就是还没有登录过,缓存中没有
//就new一个空队列,不然deque对象为空,会报空指针
if(deque==null){
deque = new LinkedList<Serializable>();
}
//如果队列里没有此sessionId,且用户没有被踢出;放入队列
if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
//将sessionId存入队列
deque.push(sessionId);
//将用户的sessionId队列缓存
cache.put(username, deque);
}
//如果队列里的sessionId数超出最大会话数,开始踢人
while(deque.size() > maxSession) {
Serializable kickoutSessionId = null;
if(kickoutAfter) { //如果踢出后者
kickoutSessionId = deque.removeFirst();
//踢出后再更新下缓存队列
cache.put(username, deque);
} else { //否则踢出前者
kickoutSessionId = deque.removeLast();
//踢出后再更新下缓存队列
cache.put(username, deque);
}
try {
//获取被踢出的sessionId的session对象
Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
if(kickoutSession != null) {
//设置会话的kickout属性表示踢出了
kickoutSession.setAttribute("kickout", true);
}
} catch (Exception e) {//ignore exception
}
}
//如果被踢出了,直接退出,重定向到踢出后的地址
if (session.getAttribute("kickout") != null) {
//会话被踢出了
try {
//退出登录
subject.logout();
} catch (Exception e) { //ignore
}
saveRequest(request);
Map<String, String> resultMap = new HashMap<String, String>();
//判断是不是Ajax请求
if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) {
resultMap.put("user_status", "300");
resultMap.put("message", "您已经在其他地方登录,请重新登录!");
//输出json串
out(response, resultMap);
}else{
//重定向
WebUtils.issueRedirect(request, response, kickoutUrl);
}
return false;
}
return true;
}
private void out(ServletResponse hresponse, Map<String, String> resultMap)
throws IOException {
try {
hresponse.setCharacterEncoding("UTF-8");
PrintWriter out = hresponse.getWriter();
out.println(JSON.toJSONString(resultMap));
out.flush();
out.close();
} catch (Exception e) {
System.err.println("KickoutSessionFilter.class 输出JSON异常,可以忽略。");
}
}
}
9.Controller层
import com.jbl.springboot_redis_shiro.server.ShiroServer;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author yfj
* @version 1.0
* @date 2020/04/15 下午 02:37
* @description: 控制层
*/
@RequestMapping("/shiro")
@Controller
public class ControllerDemo {
@Autowired
private ShiroServer shiroServer;
@RequestMapping("/login")
public String login(){
return "login";
}
//被踢出后跳转的页面
@RequestMapping("/kickout")
public String kickout(){
return "kickout";
}
@RequestMapping("/submitLogin")
public String submitLogin(String username, String password, Model model) {
//加密格式
String hashAlgorithmName="MD5";
//用户输入的密码
String credentials =password;
//盐值
Object salt = ByteSource.Util.bytes (username) ;
//加密次数
int hashIterations = 1024;
//加密
Object result =new SimpleHash(hashAlgorithmName,credentials, salt, hashIterations);
System.out.println(result);
try {
//使用shiro 编写认证操作
UsernamePasswordToken token = new UsernamePasswordToken(username, String.valueOf(result));
//拿到subject
Subject subject = SecurityUtils.getSubject();
// 执行登陆方法
subject.login(token);
// 执行到这里说明用户已登录成功
return "redirect:/shiro/index";
}catch (IncorrectCredentialsException e) {
model.addAttribute("msg", "密码错误");
return "login";
}catch (UnknownAccountException e){ //如果有登陆失败就会跳入这里
model.addAttribute("msg","用户名不存在"); //UnknownAccountException(用户不存在)
return "login";
}
}
@RequestMapping("/tianjia")
public String tianjia(){
return "user/tianjai";
}
@RequestMapping("/update")
public String update(){
return "user/update";
}
@RequestMapping("/index")
public String index(){
return "user/index";
}
@RequestMapping("/wuquan")
public String wuquan(){
return "yichang";
}
}
10.页面
1.index.html (主页面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div shiro:hasPermission="insert">
<a href="/shiro/tianjia">进入用户添加功能</a><br/>
</div>
<div shiro:hasPermission="update">
<a href="/shiro/update">进入用户修改功能</a><br/>
</div>
<div shiro:hasPermission="select">
<a>显示</a>
</div>
</body>
</html>
2.tianjii.html(添加页面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>添加</h1>
</body>
</html>
3.update (修改页面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>修改</h1>
</body>
</html>
4.异常页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>异常页面</h1>
</body>
</html>
5.多台设备登陆此账号提醒页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>多台设备登陆此账号提醒页面</h1>
</body>
</html>
6.登陆页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆页面</title>
</head>
<body>
<font style="color:red" th:text="${msg}"></font>
<form action="/shiro/submitLogin" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
<input type="submit" value="登陆"/>
</form>
</body>
</html>
7.无权限访问提醒页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>你无权访问这个页面</h1>
</body>
</html>