用户登录实现
单点登录原理
需求分析
说明:采用将Session保存到一台服务器的做法,在大型网站构建中,是不合理的.因为Session数据不同的服务器之间不能共享.会导致用户在访问网站时,会有多次的校验.如果该服务器中没有用户信息,则会出现用户重复登录的问题.
想法:
- 只要用户登陆,添加标识符
url:www.jt.com/items/1222.html?sessionId=11111
URL重写技术: - IP_hash 1.负载不均服务器宕机直接影响用户的体验
原理介绍
实现步骤:
- 用户输入用户名和密码通过前台服务器进行用户登陆操作.
- 当前台接收到用户的登陆请求后,将数据传递到单点登录系统.之后进行校验.
- 根据用户名和密码进行数据校验,如果用户名和密码都不正确,直接告知(201)用户用户名或者密码错误.
如果用户名和密码正确的,则通过加密算法(md5hash)将key进行加密,通过将用户信息转化为JSON数据保存到redis中 - 将加密后台的秘钥(TOKEN)返回给用户,将用户token经过前台中转,最终将token数据保存到用户的浏览器的Cookie中.
- 登录后需要实现单点登录效果
单点登录介绍
单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
单点登录实现
页面分析
1.页面的url
2.分析页面js
接口文档定义
1.2.2 接口文档定义
请求方法 POST
URL http://sso.jt.com/user/login
参数 u 用户名 ,p 密码
示例 http://sso.jt.com/user/login
u: chenchen
p: 123456
返回值 {
status: 200
msg: “OK”
data:” e10adc3949ba59abbe56e057f20f883e” //登录成功,返回ticket
}
备注 登录完成,返回ticket,前台系统写入cookie
ticket算法 唯一标识每个用户:动态唯一
安全:混淆md5加密
md5(“JT_TICKET_” + System.currentTime + username)
编辑前台Controller
/**
* 实现用户登陆操作
* @param user
* @return
* 核心:将用户token秘钥写入浏览器cookie中
*
* cookie.setMaxAge(3600 * 24 * 7); //7天超时
* cookie.setMaxAge(0); //立即删除
* cookie.setMaxAge(-1); //当前会话关闭后删除
*
*/
@RequestMapping("/doLogin")
@ResponseBody
Public SysResult findUserByUP(User user, HttpServletResponse response){
try {
String token = userService.findUserByUP(user);
if(StringUtils.isEmpty(token)){
returnSysResult.build(201,"用户名或密码错误");
}
//如果程序执行到这里表示token一定不为null,将数据写入cookie
Cookie cookie = new Cookie("JT_TICKET", token);
cookie.setMaxAge(3600 * 24 * 7); //7天超时
cookie.setPath("/"); //表示cookie的所有者
response.addCookie(cookie); //将cookie写入浏览器
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201,"用户名和密码错误");
}
编辑前台Service
/**
* 1.定义url
* 2.封装参数 Map
* 3.发起请求之后校验用户返回值是否正确
* 获取token数据
* 4.将正确的数据返回
*/
@Override
public String findUserByUP(User user) {
String token = null;
String url = "http://sso.jt.com/user/login";
Map<String,String> params = new HashMap<>();
params.put("username",user.getUsername());
//根据需求实现应该加密后传输需求不明多问???
String md5Pass = DigestUtils.md5Hex(user.getPassword());
params.put("password", md5Pass);
//发起http请求
String resultJSON = httpClient.doPost(url, params);
try {
SysResult sysResult = objectMapper.readValue(resultJSON,SysResult.class);
if(sysResult.getStatus() == 200){
token = (String) sysResult.getData();
}
} catch (Exception e) {
e.printStackTrace();
thrownewRuntimeException();
}
return token;
}
编辑后台Controller
@RequestMapping("/login")
@ResponseBody
Public SysResult findUserByUP(User user){
try {
String token = userService.findUserByUP(user);
if(!StringUtils.isEmpty(token))
return SysResult.oK(token);
} catch (Exception e) {
e.printStackTrace();
}
Return SysResult.build(201,"用户名或密码错误");
}
编辑后台Service
/**
* 1.根据用户名和密码查询数据库
* 如果结果为null 表示用户名或密码错误 throw
* 2.如果用户信息不为null
* 1.生成加密token
* 2.将user对象转化为JSON
* 3.将数据token:json存入redis集群中
* 4.将token数据返回
*/
@Override
public String findUserByUP(User user) {
User userDB = userMapper.findUserByUP(user);
if(userDB == null){
throw new RuntimeException();
}
String token = DigestUtils.md5Hex("JT_TICKET_"+System.currentTimeMillis()+user.getUsername());
try {
String userJSON = objectMapper.writeValueAsString(userDB);
jedisCluster.setex(token,3600 * 7 * 24,userJSON);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return token;
}
登录测试
用户信息回显
页面分析
- 说明:用户信息回显时根据token数据进行数据的回显.
- 页面JS
编辑后台Controller
//根据token数据查询用户信息
@RequestMapping("/query/{token}")
@ResponseBody
public MappingJacksonValue findUserByToken(
@PathVariable String token,Stringcallback){
String userJSON = jedisCluster.get(token);
MappingJacksonValue jacksonValue = null;
if(StringUtils.isEmpty(userJSON)){
//如果缓存数据没有则直接201返回即可
jacksonValue = newMappingJacksonValue(SysResult.build(201,"用户查询失败"));
}else{
jacksonValue = newMappingJacksonValue(SysResult.oK(userJSON));
}
jacksonValue.setJsonpFunction(callback);
return jacksonValue;
}
登录面试题
- 如何保证用户登陆顺延7天.
当用户根据token检索数据时,重新设定超时时间7天
如何需要修改cookie数据,应该从前台Controller-后台Controller - 如何保证同时只有一个用户登陆.
用户mac信息http请求能否获取???不可以!!
通过硬件设备获取用户mac交换机/路由器路由器名称一定要隐藏
设定mac拦截
输入mac
获取用户IP地址可以将IP当做一个校验的规则进行判断.
Md5(Username):IP|userJSON
实现用户登出
思考
实现用户登出操作,需要将浏览器中的cookie删除/删除redis中缓存数据.
登出的实现
/**
* 1.通过JT_TICKET或者token数据
* 2.根据token删除redis缓存数据
* 3.将cookie删除
* @return
*/
@RequestMapping("/logout")
public String logout(HttpServletRequest request,HttpServletResponse response){
//1.如何获取JT_TICKET值???
Cookie[] cookies = request.getCookies();
String token = null;
for (Cookie cookie :cookies) {
if("JT_TICKET".equals(cookie.getName())){
token = cookie.getValue();
break;
}
}
if(!StringUtils.isEmpty(token)){
//表示token数据不为null
jedisCluster.del(token);
//删除cookie
Cookie cookie = new Cookie("JT_TICKET","");
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
}
//重定向到商城主页
return"redirect:/index.html";
}
实现购物车
创建项目
根据jt-sso中的配置文件创建jt-cart
- 项目创建
- 配置nginx
server {
listen 80;
server_name cart.jt.com;
location / {
proxy_pass http://localhost:8094;
proxy_connect_timeout 3;
proxy_read_timeout 3;
proxy_send_timeout 3;
}
}
导入配置文件
- 修改web.xml
- 修改Spring配置文件
- 修改Mycat配置文件
- 修改映射文件路径
编辑POJO对象
实现购物车展现
实现购物车页面跳转
url:http://www.jt.com/cart/show.html
接口文档定义
请求方法 GET
URL http://cart.jt.com/cart/query/{userId}
参数 userId用户ID
示例 http://cart.jt.com/cart/query/1
返回值 {
status: 200 //200 成功,201 没有查到
msg: “OK” //返回信息消息
data:
[1]
0:
{
created: 1418092628000
updated: 1418092628000
id:1
userId:1
itemId:39
itemTitle: “java核心技术”
itemImage: http://image.jt.com/images/2015/06/11/20150309118.jpg
itemPrice: 87200
num:1
}
}
编辑后台Controller
@Controller
@RequestMapping("/cart")
public class CartController {
@Autowired
private CartService cartService;
//根据用户Id查询购物车信息
@RequestMapping("/query/{userId}")
@ResponseBody
public SysResult findCartByUserId(@PathVariable Long userId){
try {
List<Cart> cartList = cartService.findCartByUserId(userId);
return SysResult.oK(cartList);
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201,"购物车数据查询失败");
}
}
编辑后台Service
@Service
public class CartServiceImpl implements CartService {
@Autowired
private CartMapper cartMapper;
@Override
public List<Cart> findCartByUserId(Long userId) {
Cart cart = newCart();
cart.setUserId(userId);
return cartMapper.select(cart);
}
}
后台测试
编辑前台Controller
//跳转到购物车展现页面
@RequestMapping("/show")
public String show(Model model){
//根据userId查询购物车信息
Long userId = 7L;
List<Cart> cartList = cartService.findCartByUserId(userId);
//将cartList数据保存到request对象中
model.addAttribute("cartList", cartList);
return"cart";
}
编辑前台Service
@Override
public List<Cart> findCartByUserId(Long userId) {
String url = "http://cart.jt.com/cart/query/"+userId;
String resultJSON = httpClient.doGet(url);
List<Cart> cartList = null;
try {
SysResult sysResult = objectMapper.readValue(resultJSON, SysResult.class);
cartList = (List<Cart>) sysResult.getData();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return cartList;
}
新增购物车
页面分析
- url地址
- 页面js
<form id="cartForm" method="post">
<input class="text" id="buy-num" name="num" value="1" onkeyup="setAmount.modify('#buy-num');"/>
<input type="hidden" class="text" name="itemTitle" value="${item.title }"/>
<input type="hidden" class="text" name="itemImage" value="${item.images[0]}"/>
<input type="hidden" class="text" name="itemPrice" value="${item.price}"/>
</form>
编辑前台Controller
//实现购物车新增
/**
* http://www.jt.com/cart/add/562379.html
*/
@RequestMapping("/add/{itemId}")
public String saveCart(@PathVariable Long itemId,Cartcart){
Long userId = 7L;
cart.setItemId(itemId);
cart.setUserId(userId);
cartService.saveCart(cart);
//重定向到购物车展现页面
return "redirect:/cart/show.html";
}
编辑前台Service
/**
* 思考:如果一个对象中有100个属性问怎么传参
*/
@Override
public void saveCart(Cart cart) {
String url = "http://cart.jt.com/cart/save";
Map<String,String> params = new HashMap<>();
params.put("userId",cart.getUserId()+"");
params.put("itemId",cart.getItemId()+"");
params.put("itemTitle",cart.getItemTitle());
params.put("itemImage",cart.getItemImage());
params.put("itemPrice",cart.getItemPrice()+"");
params.put("num",cart.getNum()+"");
httpClient.doPost(url, params);
//理论上需要对返回值进行处理,如果201报错需要告知用户js暂时没有实现
}
编辑后台Controller
//实现购物车入库
@RequestMapping("/save")
publicSysResultsaveCart(Cart cart){
try {
cartService.saveCart(cart);
returnSysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
returnSysResult.build(201, "购物车新增失败");
}
编辑Service
/**
* 1.根据userId和itemId判断数据库中是否有该购物信息
* 有:
* 应该数据库num + 新的num做更新操作
* 没有数据:
* 应该插入数据库
*/
@Override
publicvoidsaveCart(Cart cart) {
Cart cartDB = cartMapper.findCartByUI(cart);
if(cartDB == null){
cart.setCreated(new Date());
cart.setUpdated(cart.getCreated());
cartMapper.insert(cart);
}else{
intnum = cartDB.getNum() + cart.getNum();
cartDB.setNum(num);
cartDB.setUpdated(newDate());
cartMapper.updateByPrimaryKeySelective(cartDB);
}
}