SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
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=7 DEFAULT CHARSET=utf8 COMMENT='用户表';
package com.taotao.manager.pojo;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Table(name = "tb_user")
public class User extends BasePojo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String phone;
private String email;
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;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + ", phone="
+ phone + ", email=" + email + "]";
}
}
package com.taotao.sso.mapper;
import com.github.abel533.mapper.Mapper;
import com.taotao.manager.pojo.User;
/**
* 用户的持久层接口
* @author Administrator
*
*/
public interface UserMapper extends Mapper {
}
package com.taotao.sso.service;
/**
* 用户的业务层接口
*
* @author Administrator
*
*/
public interface UserService {
}
package com.taotao.sso.service.impl;
import org.springframework.stereotype.Service;
import com.taotao.sso.service.UserService;
/**
* 用户业务层实现类
* @author Administrator
*
*/
@Service
public class UserServiceImpl implements UserService {
}
package com.taotao.sso.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.taotao.sso.service.UserService;
/**
* 用户的Controller
* @author Administrator
*
*/
@Controller
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
}
(1)、开发分析
接口文档
接口是校验username、phone、email三个字段是否可用,用户要输入的这三个字段必须是没有被别人使用过的。
我们可以把这个字段作为条件进行查询,如果查询到了,则表示已经有人用了,那么新用户就不能使用,数据不可用,返回false;如果没有查询到,则表示没有被别人用,新用户可以使用,数据可用,返回true。
(2)、实现Controller
/**
* 校验数据是否可用
*
* @param param
* @param status
* @return
*/
@RequestMapping(value = "check/{param}/{type}", method = RequestMethod.GET)
public ResponseEntity check(@PathVariable String param, @PathVariable Integer type) {
try {
Boolean bool = this.userService.check(param, type);
// 如果成功,返回200
return ResponseEntity.ok(bool);
} catch (Exception e) {
e.printStackTrace();
}
// 失败返回500
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
(3)、实现Service接口和实现类
UserService.java
package com.taotao.sso.service;
/**
* 用户的业务层接口
*
* @author Administrator
*
*/
public interface UserService {
/**
* 校验数据是否可用
*
* @param param
* @param status
* @return
*/
public Boolean check(String param, Integer type);
}
package com.taotao.sso.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.taotao.manager.pojo.User;
import com.taotao.sso.mapper.UserMapper;
import com.taotao.sso.service.UserService;
/**
* 用户业务层实现类
*
* @author Administrator
*
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
/**
* 校验数据是否可用
*
* @param param
* @param status
* @return
*/
public Boolean check(String param, Integer type) {
// 声明查询条件
User user = new User();
// 设置条件
switch (type) {
case 1:
user.setUsername(param);
break;
case 2:
user.setPhone(param);
break;
case 3:
user.setEmail(param);
break;
default:
break;
}
// 执行查询
Boolean bool = this.userMapper.selectCount(user) == 0;
return bool;
}
}
(1)、开发分析
根据ticket查询用户
用户信息保存在redis中,返回的数据是json格式,需要把json格式数据转为对象
(2)、实现Controller
/**
* 根据ticket查询用户
*
* @param ticket
* @return
*/
@RequestMapping(value = "{ticket}", method = RequestMethod.GET)
public ResponseEntity queryUserByTicket(@PathVariable String ticket) {
try {
User user = this.userService.queryUserByTicket(ticket);
return ResponseEntity.ok(user);
} catch (Exception e) {
e.printStackTrace();
}
// 失败返回500
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
(3)、实现Service层接口和实现类
/**
* 根据ticket查询用户
*
* @param ticket
* @return
*/
public User queryUserByTicket(String ticket);
@Autowired
private RedisUtils redisUtils;
@Value("${TAOTAO_SSO_TICKET_PRE}")
private String TAOTAO_SSO_TICKET_PRE;
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 根据ticket查询用户
*
* @param ticket
* @return
*/
public User queryUserByTicket(String ticket) {
//从redis中根据ticket查询用户信息,查询到的是json数据
//需要给redis中的key添加前缀,方便redis的维护和管理
String json = this.redisUtils.get(TAOTAO_SSO_TICKET_PRE + ticket);
//判断json是否为null
if(StringUtils.isNotBlank(json)){
try {
//把json转换为对象
User user = MAPPER.readValue(json, User.class);
//如果用户调用该方法,表示用户是活动的,需要重新设置redis中用户的缓存时间,避免每隔固定时间,用户就要重新登录
this.redisUtils.expire(TAOTAO_SSO_TICKET_PRE + ticket, 60*30);
//返回用户对象
return user;
} catch (Exception e) {
e.printStackTrace();
}
}
//如果没有查询到数据,或者转换异常,返回null
return null;
}
(4)、在redis资源配置文件中配置前缀
#配置redis中用户数据前缀
TAOTAO_SSO_TICKET_PRE=TAOTAO_SSO_TICKET_PRE_
package com.taotao.portal.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 通用页面跳转方法
*
* @author Administrator
*
*/
@Controller
@RequestMapping("page")
public class PageController {
/**
* 通用页面跳转的方法
*
* @param pageName
* @return
*/
@RequestMapping(value = "{pageName}", method = RequestMethod.GET)
public String toPage(@PathVariable String pageName) {
return pageName;
}
}
(1)、查看发送ajax请求的url
(2)、在项目中搜索 /user/check 的ajax 所在位置
(3)、查看ajax逻辑,找到 jsonp 所在位置
分析:
分析发现使用的dataType是jsonp,这里使用的是jsonp技术
为什么要使用jsonp呢,这里使用jsonp是为了解决js跨域调用的问题。
(1)、什么是跨域
跨域就是跨域名或跨端口号进行调用,例如:
www.taotao.com >> www.jd.com 是跨域
www.taotao.com >> sso.taotao.com 是跨域
www.taotao.com >> www.taotao.com:8080 是跨域
www.taotao.com >> www.taotao.com 不是跨域
只有请求者和被请求者的域名和端口号完全一致,才不是跨域
(2)、跨域有什么问题
js使用Ajax请求进行跨域请求,无法返回数据(json、xml等)。
(3)、为什么会有跨域问题
浏览器基于安全考虑,不允许Ajax请求跨域调用数据(json、xml等)
我们现在碰到的问题就是,www.taotao.com请求sso.taotao.com,因为跨域问题,是无法使用Ajax获取数据的
(1)、jsonp核心原理
js使用Ajax无法跨域调用数据(xml,json等),但是可以跨域调用js数据,js的Ajax请求数据和请求js数据的不同如下图:
(2)、解决js使用Ajax无法跨域获取数据的方法
我们可以通过请求js的方式,进行跨域调用,如下图:
修改单点登录接口,使其支持跨域其实就是使用request获取方法名,再把返回值进行包裹。
我们需要做的事,如下图:
分析发现,我们需要的请求参数名其实就是callback
/**
* 校验数据是否可用
*
* @param param
* @param status
* @return
*/
@RequestMapping(value = "check/{param}/{type}", method = RequestMethod.GET)
public ResponseEntity check(String callback, @PathVariable String param, @PathVariable Integer type) {
try {
Boolean bool = this.userService.check(param, type);
//1.接收请求中的callback的值
//2.判断callback是否为null,如果为null,则表示是普通的访问
String result = "";
if(StringUtils.isNotBlank(callback)){
//如果部位null,表示是jsonp请求
//3.使用callback包裹返回的json数据,实现跨域调用
result = callback + "(" + bool +")";
}else{
//不是jsonp请求,直接返回数据
result = "" + bool;
}
// 如果成功,返回200
return ResponseEntity.ok(result);
} catch (Exception e) {
e.printStackTrace();
}
// 失败返回500
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
(1)、在taotao-portal中加入sso-interface的依赖
(2)、在taotao-portal中编写UserConterller.java
package com.taotao.portal.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.taotao.manager.pojo.User;
import com.taotao.sso.service.UserService;
/**
* 用户功能的web类
*
* @author Administrator
*
*/
@Controller
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
/**
* 用户注册的方法
*
* @param user
* @return
*/
@RequestMapping(value = "doRegister", method = RequestMethod.POST)
@ResponseBody
public Map register(User user) {
this.userService.saveUser(user);
Map map = new HashMap();
map.put("status", 200);
return map;
}
}
/**
* 用户注册的方法
*
* @param user
*/
public void saveUser(User user);
/**
* 用户注册的方法
*/
public void saveUser(User user) {
// 设置用户信息
user.setCreated(new Date());
user.setUpdated(user.getCreated());
// 需要对密码进行加密,加密方法用的是md5Hex(),DigestUtils是commons-codecjar包中的方法
user.setPassword(DigestUtils.md5Hex(user.getPassword()));
// 执行保存方法
this.userMapper.insert(user);
}
发现问题:数据保存成功,但页面返回值错误
问题分析:
springmvc底层机制是如果请求路径以.html结尾,系统默认以html的方式响应
问题解决:修改portal项目中web.xml中拦截的路径,添加 /service/* 的拦截路径
同时:修改注册js的url
修改之后从新启动portal项目测试
package com.taotao.portal.utils;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* Cookie 工具类
*
*/
public final class CookieUtils {
/**
* 得到Cookie的值, 不编码
*
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName) {
return getCookieValue(request, cookieName, false);
}
/**
* 得到Cookie的值,
*
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null) {
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
if (isDecoder) {
retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
} else {
retValue = cookieList[i].getValue();
}
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return retValue;
}
/**
* 得到Cookie的值,
*
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null) {
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return retValue;
}
/**
* 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue) {
setCookie(request, response, cookieName, cookieValue, -1);
}
/**
* 设置Cookie的值 在指定时间内生效,但不编码
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage) {
setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
}
/**
* 设置Cookie的值 不设置生效时间,但编码
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, boolean isEncode) {
setCookie(request, response, cookieName, cookieValue, -1, isEncode);
}
/**
* 设置Cookie的值 在指定时间内生效, 编码参数
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, boolean isEncode) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
}
/**
* 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, String encodeString) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
}
/**
* 删除Cookie带cookie域名
*/
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName) {
doSetCookie(request, response, cookieName, "", -1, false);
}
/**
* 设置Cookie的值,并使其在指定时间内生效
*
* @param cookieMaxage cookie生效的最大秒数
*/
private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
try {
if (cookieValue == null) {
cookieValue = "";
} else if (isEncode) {
cookieValue = URLEncoder.encode(cookieValue, "utf-8");
}
Cookie cookie = new Cookie(cookieName, cookieValue);
if (cookieMaxage > 0)
cookie.setMaxAge(cookieMaxage);
if (null != request) {// 设置域名的cookie
String domainName = getDomainName(request);
System.out.println(domainName);
if (!"localhost".equals(domainName)) {
cookie.setDomain(domainName);
}
}
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 设置Cookie的值,并使其在指定时间内生效
*
* @param cookieMaxage cookie生效的最大秒数
*/
private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
try {
if (cookieValue == null) {
cookieValue = "";
} else {
cookieValue = URLEncoder.encode(cookieValue, encodeString);
}
Cookie cookie = new Cookie(cookieName, cookieValue);
if (cookieMaxage > 0)
cookie.setMaxAge(cookieMaxage);
if (null != request) {// 设置域名的cookie
String domainName = getDomainName(request);
System.out.println(domainName);
if (!"localhost".equals(domainName)) {
cookie.setDomain(domainName);
}
}
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 得到cookie的域名
*/
private static final String getDomainName(HttpServletRequest request) {
String domainName = null;
String serverName = request.getRequestURL().toString();
if (serverName == null || serverName.equals("")) {
domainName = "";
} else {
serverName = serverName.toLowerCase();
serverName = serverName.substring(7);
final int end = serverName.indexOf("/");
serverName = serverName.substring(0, end);
final String[] domains = serverName.split("\\.");
int len = domains.length;
if (len > 3) {
// www.xxx.com.cn
domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
} else if (len <= 3 && len > 1) {
// xxx.com or xxx.cn
domainName = "." + domains[len - 2] + "." + domains[len - 1];
} else {
domainName = serverName;
}
}
if (domainName != null && domainName.indexOf(":") > 0) {
String[] ary = domainName.split("\\:");
domainName = ary[0];
}
return domainName;
}
}
@Value("${COOKIE_TICKET}")
private String COOKIE_TICKET;
/**
* 用户登录的方法
*
* @param username
* @param password
* @return
*/
@RequestMapping(value = "doLogin", method = RequestMethod.POST)
@ResponseBody
public Map doLogin(HttpServletRequest request, HttpServletResponse response, String username,
String password) {
// 调用服务,登录用户,返回登录成功所需要的ticket
String ticket = this.userService.doLogin(username, password);
// 创建集合,存放返回数据
Map map = new HashMap();
// 判断ticket是否为null
if (StringUtils.isNotBlank(ticket)) {
// 不为空,把ticket放入cookie中
CookieUtils.setCookie(request, response, this.COOKIE_TICKET, ticket, 60 * 60 * 24, true);
// 封装返回数据
map.put("status", 200);
} else {
// 为空,登录失败,返回结果中不存放任何数据
}
return map;
}
/**
* 用户登录的方法
*
* @param user
*/
public String doLogin(String username, String password);
@Value("${TAOTAO_SSO_TICKET_INCR}")
private String TAOTAO_SSO_TICKET_INCR;
/**
* 用户登录的方法
*/
public String doLogin(String username, String password) {
//声明查询条件
User user = new User();
user.setUsername(username);
user.setPassword(DigestUtils.md5Hex(password));
//根据用户名密码执行查询方法
User result = this.userMapper.selectOne(user);
//判断用户是否登录成功
if(result != null){
//利用redis单线程的特点,生成唯一的标识ticket ticket=随机数+用户id
long incr = this.redisUtils.incr(this.TAOTAO_SSO_TICKET_INCR);
String ticket = String.valueOf(incr) + result.getId();
//把ticket和用户信息保存到redis中
try {
this.redisUtils.set(TAOTAO_SSO_TICKET_PRE + ticket, MAPPER.writeValueAsString(result),60 * 30);
//返回数据
return ticket;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
问题出现:可以登录成功,但是cookies中没有ticket的cookie
问题原因:使用debug查看存储cookie过程中,获取的域名为127.0.0.1
问题分析:因为nginx反向代理的时候,没有携带域名
在nginx配置文件中加上域名的反向代理