1.需求
1.1 情景为单一域名。
1.2 当用户在浏览器页面登陆成功后,如果这时再在同一个浏览器打开一个登陆页面进行登陆,那么就需要删除前一个页面的登陆的信息。
这时再在前一个页面进行操作,就会发现需要再一次的登陆。在以上情况下,如果打开另一个浏览器页面惊喜登陆,需要把之前的浏览器登陆信息删除,
当用户在前一个浏览器进行操作时会发现需要再一次的登陆,同样的,如果用户在这个浏览器进行了登陆,那么另外一个浏览器的登陆信息需要被删除。
2 .分析解决
在这里首先设定用户admin在一个浏览器上已经登陆过了,登陆过之后就会存入一些信息到cookie和redis中。那么这里该怎么存呢?
因为条件是必须保证单一登陆,类似于PC上的QQ,所以这里先考虑在同一浏览器上的情况,既然是在同一浏览器了,那么需要保证单一性就比较好写了。
我的写法是用户登陆后将用户名作为cookie的键,使用uuid或者雪花算法随机生成一个值,写入到前端,在以cookie的值作为键,再随机生成一个数值作为 值存入到redis中,保证当前用户只在一个地方进行了登陆。这里选择将用户id做为cookie的键的原因是因为后面还需要考虑到不同浏览器的问题。
用户admin第一次登陆成功后,就有信息存在在redis中和cookie中了。当admin在打开同一浏览器的一个页面进行登陆时,就可以把cookie带入到请求中, 在请求的过程中,使用拦截器将请求拦截下来,因为是登陆请求,那么就肯定可以获取到用户名,在这里就可以根据用户名获取到Cooke的值,而根据cookie 的值就可以获取到redis的值,在新页面上登陆后,把redis中的信息删除和cookie删除。
这个时候逻辑还是有问题的,我们是无法确定用户已经登陆过了,前面的假设是为了更好理解。也就是说我们是无法确定cookie是否存在,那么我们就需要
在拦截器里加判断,判断cookie是否存在,如果存在那么就可以肯定用户是已经登陆过的了,只需要做上面的操做即可。这个时候需要加一点新的逻辑,用户在 同一个浏览第二次登陆时,会把第一次的登陆信息删掉,而这时后用户再去第一次登陆过的页惊喜操作,拦截会把请求拦截下来,在删除前一次登陆的信息后, 重定向到登陆页面,拦截器不放行,不继续执行loginController。
以上分析是基于在同一个浏览器的情况下,当不在同一浏览器时是什么情况呢?首先可以肯定的是cookie肯定是不存在的,如果是在同一个浏览器下登陆 cookie怎么可能不存在呢?但是cookie不存在的情况又可以分为两种情景,一种是用户首先在其他浏览器进行了登陆,在来这个浏览器进行登陆,另一种情况 是用户真的才刚刚登陆。那么着重看第一种情况,第一种情况怎么解决呢?需要在不同浏览器的到用户的信息并进行操作。
那么我是在用户登陆的时候,将用户名作为键,cookie的值作为value存入了redis。因为通过cookie的值就可以操作用户的唯一信息了。在cookie不存在的 情况下再进行判断,如果用户在Reids中有信息,则意味着他前面已经登陆过了,若不存在直接放行,如果已经登陆过了,删除之前的信息,在进行登陆。
3. 主要代码
拦截器
在使用拦截器时需要注意的点是,因为我需要在拦截中使用jedis,当拦截器的执行顺序在注入bean之前。所以需要做一些操作:参考,当然,代码里已经改好了。
package com.los.sso.interceptor; import com.los.sso.jedis.JedisDao; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class LoginIntecerptor implements HandlerInterceptor { private JedisDao jedisDao; public LoginIntecerptor(JedisDao jedisDao) { this.jedisDao = jedisDao; } ////进入Controller之前执行该方法 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { System.out.println("---》登陆拦截请求器"); /** * * case1: * cookie存在且和redis中的相同 * 同一浏览器重复登陆,删除上一次登陆信息后放行 * case2: * resis中的token不存在 * 可能是打开了另一个浏览器进行了登陆,删除上一次登陆后的信息,在放行 * 如果信息不存在,则是另一个账号在进行登陆,直接放行 * */ //获取用户已存在redis中的令牌 //拿到sessionid //拿到用户登陆名 String username = request.getParameter("username"); //Object username1 = request.getAttribute("username"); if(StringUtils.isEmpty(username)){//用户名为空时 request.setAttribute("status",404); response.sendRedirect("login"); return false; } boolean flag = true; String redisvalue = ""; //是否存在token Cookie[] cookies = request.getCookies(); for (Cookie cookie:cookies) { if(cookie.getName().equals(username)){ redisvalue = cookie.getValue(); //token的值是redis中用户信息的key flag = false;//存在token } } //根据token的值判断是否是在同一浏览器是第二次登陆 if(!flag){ //token存在的情况 String value = jedisDao.getValue(redisvalue); if(!StringUtils.isEmpty(value)){//不为空时, jedisDao.delValue(redisvalue); //删除上一次登陆存在redis中的信息 for (Cookie cookie:cookies) { if(cookie.getName().equals(username)){ cookie.setValue(null); response.addCookie(cookie); break; } } //进入登陆controller response.sendRedirect("index"); return false; } //response.sendRedirect("index"); return true; }else { /** * token不存在的情况 * 在浏览器上从未登陆过,但也有可能在其他浏览器登陆过了,需要删除存在redis中的值。 */ //首先判断该用户是否已经登陆过 String islogin = jedisDao.getValue(username); if(jedisDao.getValue(username)!=null&&!jedisDao.getValue(username).equals("")){ jedisDao.delValue(jedisDao.getValue(username)); //清空params //清空Attribute response.sendRedirect("index"); return false; } //另外一个账号登陆 return true; } } //处理请求完成后视图渲染之前的处理操作 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { } //视图渲染之后的操作 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { } }
注册拦截器
package com.los.sso.config; import com.los.sso.interceptor.LoginIntecerptor; import com.los.sso.jedis.JedisDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Autowired private JedisDao jedisDao; //需要被拦截的路径 @Override public void addInterceptors(InterceptorRegistry registry) { String[] addPathPatterns = { "/in_login" }; //不需要拦截的路径 String [] excludePathPaterns={ "/index" }; //注册一个拦截器 registry.addInterceptor(new LoginIntecerptor(jedisDao)) .addPathPatterns(addPathPatterns) .excludePathPatterns(excludePathPaterns); //如果有多个拦截器只需要在添加一遍就行 } }
pom.xml 信息
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 添加jedis依赖 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.1</version> </dependency> <!-- servlet 依赖. --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency> <!-- jstl依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!--SpringBoot默认不支持JSP,需要在项目中添加相关的依赖--> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <resources> <!-- 打包时将jsp文件拷贝到META-INF目录下--> <resource> <!-- 指定处理哪个目录下的资源文件 --> <directory>src/main/webapp</directory> <!--注意此次必须要放在此目录下才能被访问到--> <targetPath>META-INF/resources</targetPath> <includes> <include>**/**</include> </includes> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
yml 配置信息
server: port: 8989 servlet: jsp: init-parameters: development: true spring: main: allow-bean-definition-overriding: true mvc: view: prefix: /WEB-INF/jsp/ suffix: .jsp datasource: url: jdbc:mysql://localhost:3306/sys username: root password: sasa driver-class-name: com.mysql.jdbc.Driver jpa: database: MYSQL show-sql: true hibernate: ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect redis: host: 你的ip password: jedis: pool: max-total: 200
实体类,使用sprigdataJpa根据实体类生成数据库表。
import lombok.Data; import javax.persistence.*; @Data @Entity @Table(name = "ssologin") public class User { @Id @Column(name = "id") private int id; @Column(name = "username") private String username; @Column(name = "password") private String password; }
项目结构
关于一些Jedis的工具网上百度的话有一大堆,这里就不贴了。