一、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
