Table of contents
1. The sharing problem of session in shiro+springboot
1.1 How to solve the session sharing problem
2.1. How to put the sessionId into the request header
2.2. Override the method of DefaultWebSessionManager
3. Set the front-end pre-routing guard
4. How to prevent malicious repeated login
6. Get the information of the currently logged in user
7. Set the number of login devices
1. Springboot integrates session sharing in Shiro
Problem demo:
(1) Start the shiro-springboot cluster project
two boots
(2) Modify the configuration of nginx
(3) test
When accessing certain resources after successful login, a json prompt of not being logged in appears
1.1 How to solve the session sharing problem
By default, the session is stored in the memory of each service, and the session can be stored in redis uniformly.
Crazy Cake Dependency. --- Provides a class for redis to store sessions.
Modify shiro's configuration class (ShiroConfig.java)
@Bean public DefaultWebSecurityManager securityManager() { // 创建默认的 Web 安全管理器 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置安全管理器使用的 Realm securityManager.setRealm(myRealm()); securityManager.setCacheManager(redisCacheManager()); //设置session管理器 securityManager.setSessionManager(sessionManager()); // 返回安全管理器 return securityManager; } @Bean public SessionManager sessionManager(){ //session管理器 DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); //setSessionDAO用于操作session对象,在容器中对象session进行CRUD操作 sessionManager.setSessionDAO(sessionDao()); return sessionManager; } @Bean public SessionDAO sessionDao(){ //该类会对对象session进行CRUD操作 RedisSessionDAO sessionDAO = new RedisSessionDAO(); RedisManager redisManager = new RedisManager(); redisManager.setHost("192.168.75.129:6379"); sessionDAO.setRedisManager(redisManager); return sessionDAO; }
Core: DefaultWebSessionManager gets the value of JSESSIONID in the request header,
Query the key corresponding to this value from redis through RedisSessionDAO. If it exists, the current user is considered to be logged in
2. Solve the effect that the front end does not support cookies
Problem demo:
Background UserController added:
Front-end reference Vue scaffolding project
Modification of front-end Login.vue
Front-end Product.vue modification
Test: login
After logging in, click Permissions - Query, directly report an error - cross-domain
reason:
By default, the DefaultWebSessionManager only accepts the JsessionId stored in the Cookie. The query finds that there is no corresponding key in redis.
How to solve:
When the client sends a request, carry the sessionId in the request header, and then override the getSessionId() method in DefaultWebSessionManager.
Thinking: 1. How to put the sessionId into the request header.
2. How to rewrite the getSessionId method to get the sessionID of the request header.
2.1. How to put the sessionId into the request header
Modify the login interface
Modify the front-end login method
Modify the main.js file
//设置axios的请求拦截器 axios.interceptors.request.use(config=>{ //从localStorage中获取token的值 var item = localStorage.getItem("token"); if (item){ config.headers.token=item; } return config; })
Gray dependency deletion
2.2. Override the method of DefaultWebSessionManager
createMyWebSessionManager.java
import org.apache.shiro.web.servlet.ShiroHttpServletRequest; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.apache.shiro.web.util.WebUtils; import org.springframework.util.StringUtils; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.Serializable; public class MyWebSessionManager extends DefaultWebSessionManager { private static final String AUTHORIZATION = "token"; private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; protected Serializable getSessionId(ServletRequest request, ServletResponse response) { //获取请求头中名称为token的内容 String id = WebUtils.toHttp(request).getHeader("token"); if (!StringUtils.isEmpty(id)) { //如果存在该token request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "Stateless request"); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return id; } else { //从cookie中获取sessionId. return super.getSessionId(request, response); } } }
Modify shiro configuration class
Add in LoginFilter filter:
A cross-domain request is found here, and two requests will be sent: the first OPTIONS request, and the second request is a real request.
OPTIONS request: Vanguard.
So we have to release all OPTIONS requests
@Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { HttpServletRequest req = (HttpServletRequest) request; String method = req.getMethod(); if("OPTIONS".equals(method)){ return true; } return super.isAccessAllowed(request, response, mappedValue); }
3. Set the front-end pre-routing guard
//设置前置路由守护-----登录才能访问里面的资源 router.beforeEach((to,from,next)=>{ //to:到哪去 from:从哪来 next:下一站 //获取路由的路径 var path = to.path; if (path == "/login"){ return next(); } //判断是否登录过 var token = sessionStorage.getItem("token"); if (token){ return next(); } return next("/login"); })
Change localStorage to sessionStorage
4. How to prevent malicious repeated login
Add to:
5. Exit
Edit exit interface
@PostMapping("/logout") public Result logout(){ Subject subject = SecurityUtils.getSubject(); //清空redis subject.logout(); return new Result(200,"退出成功",null); }
Edit frontend exit button
6. Get the information of the currently logged in user
Edit query information interface
Edit frontend exit button
Click to get user name
7. Set the number of login devices
Demo: One account can only log in to two devices
increase dependence
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
configuration file
#配置redis spring.redis.host=192.168.75.129
LoginController@Controller //默认*允许所有域都可以跨域访问该接口 /*@CrossOrigin*/ public class LoginController { @Autowired private StringRedisTemplate redisTemplate; @PostMapping("/login") @ResponseBody public Result login(@RequestBody LoginVo loginVo) { // 获取当前主体对象 Subject subject = SecurityUtils.getSubject(); // 创建用户名密码令牌 UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getUsername(), loginVo.getPassword()); try { String key = "shiro:user:" + loginVo.getUsername(); ValueOperations<String, String> foValue = redisTemplate.opsForValue(); int count = 0; String c = foValue.get(key); if (c != null) { if (Integer.parseInt(c) >= 1) { return new Result(400, "同时在线设备不能超过2台", subject.getSession().getId()); } else { count++; } } else { count = 0; } subject.login(token); foValue.set(key, count + ""); return new Result(200, "登录成功~~~", subject.getSession().getId()); } catch (Exception e) { e.printStackTrace(); // 登录失败,重定向到登录页面 return new Result(500, "登录失败", null); } } }