单点登录系统开发

单点登录系统开发一 SSO 单点登录系统简介 基本介绍单点登录 SSO SingleSignOn 就是在一个多系统共存的环境下 用户在一处登录后 就不用在其他系统中登录 也就是用户的一次登录能得到其他所有系统的信任 单点登录在大型网站里使用得非常频繁 例如像阿里巴巴这样的网站 在网站的背后是成百上千的子系统 用户一次操作或交易可能涉及到几十个子系统的协作 如果每个子系统都需要用户认证 不仅用户会疯掉

一、SSO(单点登录系统简介)

  • 基本介绍

单点登录SSO(Single Sign On)就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任。单点登录在大型网站里使用得非常频繁,例如像阿里巴巴这样的网站,在网站的背后是成百上千的子系统,用户一次操作或交易可能涉及到几十个子系统的协作,如果每个子系统都需要用户认证,不仅用户会疯掉,各子系统也会为这种重复认证授权的逻辑搞疯掉。

  • 解决方案
  • SSO与该项目的关系

之前实现的登录和注册是在同一个tomcat内部完成,不存在单点登录的问题。现在的系统架构每个系统都是单独部署运行一个单独的tomcat,所以,不能将用户的登录信息保存到session中(多个tomcat的session是不能高效共享的),所以需要一个单独的系统来维护用户的登录信息。

二、SSO系统框架的搭建

1、搭建Maven工程

  • pom.xml内容如下:
<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>com.enjoyshop.parent 
   groupId> <artifactId>enjoyshop-parent 
   artifactId> <version>0.0.1-SNAPSHOT 
   version>  
   parent> <groupId>com.enjoyshop.sso 
   groupId> <artifactId>enjoyshop-sso 
   artifactId> <version>1.0.0-SNAPSHOT 
   version> <packaging>war 
   packaging> <dependencies> <dependency> <groupId>com.enjoyshop.common 
   groupId> <artifactId>enjoyshop-common 
   artifactId> <version>1.0.0-SNAPSHOT 
   version>  
   dependency>  
    <dependency> <groupId>junit 
   groupId> <artifactId>junit 
   artifactId> <scope>test 
   scope>  
   dependency> <dependency> <groupId>org.springframework 
   groupId> <artifactId>spring-webmvc 
   artifactId>  
   dependency> <dependency> <groupId>org.springframework 
   groupId> <artifactId>spring-jdbc 
   artifactId>  
   dependency> <dependency> <groupId>org.springframework 
   groupId> <artifactId>spring-aspects 
   artifactId>  
   dependency>  
    <dependency> <groupId>org.mybatis 
   groupId> <artifactId>mybatis 
   artifactId>  
   dependency> <dependency> <groupId>org.mybatis 
   groupId> <artifactId>mybatis-spring 
   artifactId>  
   dependency>  
    <dependency> <groupId>com.github.abel533 
   groupId> <artifactId>mapper 
   artifactId>  
   dependency>  
    <dependency> <groupId>mysql 
   groupId> <artifactId>mysql-connector-java 
   artifactId>  
   dependency> <dependency> <groupId>org.slf4j 
   groupId> <artifactId>slf4j-log4j12 
   artifactId>  
   dependency>  
    <dependency> <groupId>com.fasterxml.jackson.core 
   groupId> <artifactId>jackson-databind 
   artifactId>  
   dependency>  
    <dependency> <groupId>com.jolbox 
   groupId> <artifactId>bonecp-spring 
   artifactId>  
   dependency>  
    <dependency> <groupId>jstl 
   groupId> <artifactId>jstl 
   artifactId>  
   dependency> <dependency> <groupId>javax.servlet 
   groupId> <artifactId>servlet-api 
   artifactId> <scope>provided 
   scope>  
   dependency> <dependency> <groupId>javax.servlet 
   groupId> <artifactId>jsp-api 
   artifactId> <scope>provided 
   scope>  
   dependency>  
    <dependency> <groupId>org.apache.commons 
   groupId> <artifactId>commons-lang3 
   artifactId>  
   dependency> <dependency> <groupId>org.apache.commons 
   groupId> <artifactId>commons-io 
   artifactId>  
   dependency>  
    <dependency> <groupId>commons-codec 
   groupId> <artifactId>commons-codec 
   artifactId> <version>1.9 
   version>  
   dependency>  
    <dependency> <groupId>org.hibernate 
   groupId> <artifactId>hibernate-validator 
   artifactId> <version>5.1.3.Final 
   version>  
   dependency>  
   dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven 
   groupId> <artifactId>tomcat7-maven-plugin 
   artifactId> <configuration> <port>8083 
   port> <path>/ 
   path>  
   configuration>  
   plugin>  
   plugins>  
   build>  
   project>
  • 配置web.xml
 
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>enjoyshop-sso 
   display-name> <context-param> <param-name>contextConfigLocation 
   param-name> <param-value>classpath:spring/applicationContext*.xml 
   param-value>  
   context-param>  
    <listener> <listener-class>org.springframework.web.context.ContextLoaderListener 
   listener-class>  
   listener>  
    <filter> <filter-name>encodingFilter 
   filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter 
   filter-class> <init-param> <param-name>encoding 
   param-name> <param-value>UTF8 
   param-value>  
   init-param>  
   filter> <filter-mapping> <filter-name>encodingFilter 
   filter-name> <url-pattern>/* 
   url-pattern>  
   filter-mapping>  
    <servlet> <servlet-name>enjoyshop-sso 
   servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet 
   servlet-class> <init-param> <param-name>contextConfigLocation 
   param-name> <param-value>classpath:spring/enjoyshop-sso-servlet.xml 
   param-value>  
   init-param> <load-on-startup>1 
   load-on-startup>  
   servlet> <servlet-mapping> <servlet-name>enjoyshop-sso 
   servlet-name> <url-pattern>*.html 
   url-pattern>  
   servlet-mapping> <servlet-mapping> <servlet-name>enjoyshop-sso 
   servlet-name> <url-pattern>/service/* 
   url-pattern>  
   servlet-mapping> <welcome-file-list> <welcome-file>index.jsp 
   welcome-file>  
   welcome-file-list>  
   web-app> 
  • 进行SSM整合

这部分内容不作具体描述。可参考具体源码

  • 静态资源文件的引用方式
  • 配置nginx访问静态资源
127.0.0.1 sso.enjoyshop.com 127.0.0.1 static.enjoyshop.com
server { listen 80; server_name static.enjoyshop.com; #charset koi8-r; #access_log logs/host.access.log main; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { root E:\\enjoyshop-static; } }

2、实现用户注册功能

  • 用户表结构
CREATE TABLE `tb_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(32) NOT NULL COMMENT '密码,加密存储', `phone` varchar(20) DEFAULT NULL COMMENT '注册手机号', `email` varchar(50) DEFAULT NULL COMMENT '注册邮箱', `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) USING BTREE, UNIQUE KEY `phone` (`phone`) USING BTREE, UNIQUE KEY `email` (`email`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='用户表' 
  • pojo

这里使用Hibernate的validator来做数据校验。

package com.enjoyshop.sso.pojo; import java.util.Date; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import com.fasterxml.jackson.annotation.JsonIgnore; @Table(name = "tb_user") public class User { 
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Length(min = 6, max = 20, message = "用户名的长度必须在6~20位之间!") private String username; @JsonIgnore//json序列化时忽略该字段 @Length(min = 6, max = 20, message = "密码的长度必须在6~20位之间!") private String password; @Length(min = 11, max = 11, message = "手机号的长度必须是11位!") private String phone; @Email(message = "邮箱格式不符合规则!") private String email; private Date created; private Date updated; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Date getCreated() { return created; } public void setCreated(Date created) { this.created = created; } public Date getUpdated() { return updated; } public void setUpdated(Date updated) { this.updated = updated; } } 
  • mapper

这里使用通用mapper

package com.enjoyshop.sso.mapper; import com.enjoyshop.sso.pojo.User; import com.github.abel533.mapper.Mapper; public interface UserMapper extends Mapper<User>{ 
    } 
  • service

检测数据可用性

public Boolean check(String param, Integer type) { if (type < 1 || type > 3) { return null; } User record = new User(); switch (type) { case 1: record.setUsername(param); break; case 2: record.setPhone(param); break; case 3: record.setEmail(param); break; default: break; } return this.userMapper.selectOne(record) == null; }

注册逻辑

public Boolean saveUser(User user) { user.setId(null); user.setCreated(new Date()); user.setUpdated(user.getCreated()); // 密码通过MD5进行加密处理 user.setPassword(DigestUtils.md5Hex(user.getPassword())); return this.userMapper.insert(user) == 1; }
  • controller
    检测提交的用户信息是否已注册

@RequestMapping(value = "check/{param}/{type}", method = RequestMethod.GET) public ResponseEntity 
  
    check( 
   @PathVariable( 
   "param") String param, 
   @PathVariable( 
   "type") Integer type) { 
   try { Boolean 
   bool = 
   this.userService.check(param, type); 
   if ( 
   null == 
   bool) { 
   return ResponseEntity.status(HttpStatus.BAD_REQUEST). 
   body( 
   null); } 
   // 前端逻辑有问题,这里只有用!bool才能得到正确的结果 
   return ResponseEntity.ok(! 
   bool); } 
   catch (Exception e) { e.printStackTrace(); } 
   return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR). 
   body( 
   null); } 
  

注册逻辑

@RequestMapping(value = "register", method = RequestMethod.GET) public String toRegister() { return "register"; } @RequestMapping(value = "doRegister", method = RequestMethod.POST) @ResponseBody public Map<String, Object> doRegister(@Valid User user,BindingResult bindingResult) { Map<String, Object> result = new HashMap<String, Object>(); if(bindingResult.hasErrors()){ //校验有误 List<String> msgs=new ArrayList<String>(); List<ObjectError> allErrors = bindingResult.getAllErrors(); for (ObjectError objectError : allErrors) { String msg = objectError.getDefaultMessage(); msgs.add(msg); } result.put("status", "400"); result.put("data", StringUtils.join(msgs, '|')); return result; } Boolean bool = this.userService.saveUser(user); if (bool) { // 注册成功 result.put("status", "200"); } else { result.put("status", "300"); result.put("data", "注册失败,请重新注册!"); } return result; }
  • 可能碰到的问题

异常:

org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation 2015-11-19 11:09:57,893 [http-bio-8083-exec-2] [org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver]-[DEBUG] Resolving exception from handler [public org.springframework.http.ResponseEntity 
  
    .lang 
   .Boolean> 
   com 
   .taotao 
   .sso 
   .controller 
   .UserController 
   .check(java 
   .lang 
   .String,java 
   .lang 
   .Integer)]: 
  

解决方案:配置多条路径进入SpringMVC

  
    <servlet> <servlet-name>enjoyshop-sso 
   servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet 
   servlet-class> <init-param> <param-name>contextConfigLocation 
   param-name> <param-value>classpath:spring/enjoyshop-sso-servlet.xml 
   param-value>  
   init-param> <load-on-startup>1 
   load-on-startup>  
   servlet> <servlet-mapping> <servlet-name>enjoyshop-sso 
   servlet-name> <url-pattern>*.html 
   url-pattern>  
   servlet-mapping> <servlet-mapping> <servlet-name>enjoyshop-sso 
   servlet-name> <url-pattern>/service/* 
   url-pattern>  
   servlet-mapping>

3、实现用户登陆功能

  • service层:
public String doLogin(String username, String password) throws Exception { User record = new User(); record.setUsername(username); User user = this.userMapper.selectOne(record); if (null == user) { return null; } // 比对密码是否正确 if (!StringUtils.equals(DigestUtils.md5Hex(password), user.getPassword())) { return null; } // 登录成功 // 生存token String token = DigestUtils.md5Hex(System.currentTimeMillis() + username); // 将用户数据保存到redis中 this.redisService.set("TOKEN_" + token, MAPPER.writeValueAsString(user), 60 * 30); return token; }
  • controller层
@RequestMapping(value = "doLogin", method = RequestMethod.POST) @ResponseBody public Map<String, Object> doLogin(@RequestParam("username") String username, @RequestParam("password") String password, HttpServletRequest request, HttpServletResponse response) { Map<String, Object> result = new HashMap<String, Object>(); try { String token = this.userService.doLogin(username, password); if (null == token) { // 登录失败 result.put("status", 400); } else { // 登录成功,需要将token写入到cookie中 result.put("status", 200); CookieUtils.setCookie(request, response, COOKIE_NAME, token); } } catch (Exception e) { e.printStackTrace(); // 登录失败 result.put("status", 500); } return result; }
  • 可能碰到的问题
 @JsonIgnore//json序列化时忽略该字段 @Length(min = 6, max = 20, message = "密码的长度必须在6~20位之间!") private String password;
server { listen 80; server_name sso.enjoyshop.com; #charset koi8-r; #access_log logs/host.access.log main; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #加入头信息,使得tomcat可以正确解析URL地址 proxy_set_header Host $host; location / { proxy_pass http://127.0.0.1:8083; proxy_connect_timeout 600; proxy_read_timeout 600; } } 

4、实现登陆人信息的显示

  • 前台系统中的js展示:
var TT = enjoyshop = { checkLogin : function(){ 
    var _token = $.cookie("TT_TOKEN"); if(!_token){ return ; } $.ajax({ url : "http://sso.enjoyshop.com/service/user/" + _token, dataType : "jsonp", type : "GET", success : function(_data){ 
    var html =_data.username+",欢迎来到乐购![退出]"; $("#loginbar").html(html);
            }
        });
    }
}
  • 添加跨域请求支持
  
    <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean  class="com.enjoyshop.common.spring.exetend.converter.json.CallbackMappingJackson2HttpMessageConverter"> <property name="callbackName" value="callback" />  
   bean>  
   mvc:message-converters>  
   mvc:annotation-driven>
  • service层

根据token查询信息

public User queryUserByToken(String token) { String key = "TOKEN_" + token; String jsonData = this.redisService.get(key); if (StringUtils.isEmpty(jsonData)) { return null; } try { // 刷新用户的生存时间(非常重要) this.redisService.expire(key, 60 * 30); return MAPPER.readValue(jsonData, User.class); } catch (Exception e) { e.printStackTrace(); } return null; }

controller层

@RequestMapping(value = "{token}", method = RequestMethod.GET) public ResponseEntity 
  
    queryUserByToken( 
   @PathVariable( 
   "token") String token) { 
   try { User user = 
   this.userService.queryUserByToken(token); 
   if ( 
   null == user) { 
   return ResponseEntity.status(HttpStatus.NOT_FOUND). 
   body( 
   null); } 
   return ResponseEntity.ok(user); } 
   catch (Exception e) { e.printStackTrace(); } 
   return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR). 
   body( 
   null); } 
  
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/207029.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午2:36
下一篇 2026年3月19日 下午2:37


相关推荐

  • Springboot上传excel并将表格数据导入或更新mySql数据库[通俗易懂]

    Springboot上传excel并将表格数据导入或更新mySql数据库[通俗易懂]本文主要描述,Springboot-mybatis框架下上传excel,并将之导入mysql数据库的过程,如果用户id已存在,则进行更新修改数据库中该项信息,由于用到的是前后端分离技术,这里记录的主要是后端java部分,通过与前端接口进行对接实现功能1.在pom.xml文件中导入注解,主要利用POI<dependency><groupId>org.a…

    2022年6月28日
    30
  • pycharm如何执行高级撤销操作回到历史[通俗易懂]

    pycharm如何执行高级撤销操作回到历史[通俗易懂]今天写代码兴奋过头了,认为别人写得太麻烦,所以在看了这个人是要达成什么样的目标之后,把他的代码直接删了,然后自己重写,到后来发现有这样那样的问题,这个时候想参考原来的代码,可是为时已晚,已经是6,7个小时之前了,姑且不问能否一直使用低级撤销ctrl+z,就算可以,估计也要半个小时才能回到6,7个小时之前吧。这个时候,我悲从中来,悔恨自己在最开始的时候没有弄一个备份。但是,痛定思痛,发现了这一个撤销的高级操作,回退到历史,我以前在使用AndroidStudio的时候也有这个功能,所以试了试pycharm

    2022年8月26日
    8
  • Java后台开发知识一览

    Java后台开发知识一览Java后台开发知识一览1、后端WEB服务器:Weblogic、Tomcat、WebSphere、JBoss、Jetty核心框架:SpringFramework分布式服务框架Dubbo(感谢@浅浅浅丿忧伤指正)安全框架:ApacheShiro视图框架:SpringMVC服务端验证:Hibernate+Validator布局框架:SiteMesh工作流…

    2022年7月8日
    19
  • zencart免费模板下载

    zencart免费模板下载最近工作比较忙,没有时间专门来制作这个免费的包包模板。趁国庆放假有时间,顺便就把这个免费模板制作完了。今天特别提供出来给大家下载使用。考虑到很难满足所有有的要求,所以这个模板在一些地方基本没有修改原有模板的布局,只是简单的修改CSS。不过经过修改的这个模板也还算漂亮,大体上的布局已经设置好。我们没有那么多的时间去美化一个这样的模板,俗话说:授人鱼不如授人渔。如果有兴趣做二次开发的朋友可以继续修

    2022年7月27日
    6
  • 5分钟装好Rational Rose激活成功教程版 非常好用[通俗易懂]

    5分钟装好Rational Rose激活成功教程版 非常好用[通俗易懂]1:下载两个文件点此下载,第一个是安装包,第二个是打开这个安装包内镜像的工具。2.打开下载第二个压缩包的工具,名字是rmxngq_1272,里面有一个应用程序名字为:virtualdrivemaster,双击这个应用程序,会弹出一个界面,把下载的第一个压缩包第一个bin文件(IBM.rational….

    2022年6月22日
    59
  • Java获取系统时间的四种方法

    Java获取系统时间的四种方法1、Dateday=newDate();SimpleDateFormatdf=newSimpleDateFormat(“yyyy-MM-ddHH:mm:ss”);System.out.println(df.format(day));通过Date类来获取当前时间2、SimpleDateFormatdf=newSimpleDateF

    2022年6月14日
    30

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号