目录:
流程分析
(1)实现思路
(2)搭建认证中心模块service-user
(3)配置网关server-gateway
(4)在web-all模块添加实现
流程分析
公私钥解决:
认证中心有个私钥认证完之后,对user进行加密生成tocken,tocken里面有用户信息,再次访问购物车,购物车的公钥正好能够解析用户信息,这个就不需要购物车再次通过tocken去认证服务器询问是够用户的tocken是否正确,避免多次访问请求认证服中心,只需要登录一次就够了
(1)实现思路
- 用接收的用户名密码核对后台数据库
- 核对通过,用uuid生成token
- 将用户id加载到写入redis,redis的key为token,value为用户id。
- 登录成功返回token与用户信息,将token与用户信息记录到cookie里面
- 重定向用户到之前的来源地址。
数据库表:user_info,并添加一条数据!密码应该是加密的!
user_address
user_info
(2)搭建认证中心模块service-user
搭建方式如service-item
pom:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.atguigu.gmall</groupId>
<artifactId>service</artifactId>
<version>1.0</version>
</parent>
<version>1.0</version>
<artifactId>service-user</artifactId>
<packaging>jar</packaging>
<name>service-user</name>
<description>service-user</description>
<build>
<finalName>service-user</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
添加配置文件
bootstrap.properties
spring.application.name=service-user
spring.profiles.active=dev
spring.cloud.nacos.discovery.server-addr=192.168.254.129:8848
spring.cloud.nacos.config.server-addr=192.168.254.129:8848
spring.cloud.nacos.config.prefix=${spring.application.name}
spring.cloud.nacos.config.file-extension=yaml
spring.cloud.nacos.config.shared-configs[0].data-id=common.yaml
启动类
package com.atguigu.gmall.user;
@SpringBootApplication
@ComponentScan({"com.atguigu.gmall"})
@EnableDiscoveryClient
public class ServiceUserApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceUserApplication.class, args);
}
}
封装登录接口
编写接口
package com.atguigu.gmall.user.service;
public interface UserService {
/**
* 登录方法
* @param userInfo
* @return
*/
UserInfo login(UserInfo userInfo);
}
Mapper:UserInfoMapper
package com.atguigu.gmall.user.mapper;
import com.atguigu.gmall.model.user.UserInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
实现类
package com.atguigu.gmall.user.service.impl;
@Service
public class UserServiceImpl implements UserService {
// 调用mapper 层
@Autowired
private UserInfoMapper userInfoMapper;
@Override
public UserInfo login(UserInfo userInfo) {
// select * from userInfo where userName = ? and passwd = ?
// 注意密码是加密:
String passwd = userInfo.getPasswd(); //123
// 将passwd 进行加密
String newPasswd = DigestUtils.md5DigestAsHex(passwd.getBytes());
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("login_name", userInfo.getLoginName());
queryWrapper.eq("passwd", newPasswd);
UserInfo info = userInfoMapper.selectOne(queryWrapper);
if (info != null) {
return info;
}
return null;
}
}
IpUtil :
package com.atguigu.gmall.common.util;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 获取ip地址
*/
public class IpUtil {
public static String getIpAddress(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress="";
}
// ipAddress = this.getRequest().getRemoteAddr();
return ipAddress;
}
// 网关中获取Ip地址
public static String getGatwayIpAddress(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ip = headers.getFirst("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddress().getAddress().getHostAddress();
}
return ip;
}
}
控制器:PassportApiController
package com.atguigu.gmall.user.controller;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.util.IpUtil;
/**
* <p>
* 用户认证接口
* </p>
*/
@RestController
@RequestMapping("/api/user/passport")
public class PassportApiController {
@Autowired
private UserService userService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 登录
* @param userInfo
* @param request
* @param response
* @return
*/
@PostMapping("login")
public Result login(@RequestBody UserInfo userInfo, HttpServletRequest request, HttpServletResponse response) {
System.out.println("进入控制器!");
UserInfo info = userService.login(userInfo);
if (info != null) {
String token = UUID.randomUUID().toString().replaceAll("-", "");
HashMap<String, Object> map = new HashMap<>();
map.put("nickName", info.getNickName());
map.put("token", token);
JSONObject userJson = new JSONObject();
userJson.put("userId", info.getId().toString());
userJson.put("ip", IpUtil.getIpAddress(request));
redisTemplate.opsForValue().set(RedisConst.USER_LOGIN_KEY_PREFIX + token, userJson.toJSONString(), RedisConst.USERKEY_TIMEOUT, TimeUnit.SECONDS);
return Result.ok(map);
} else {
return Result.fail().message("用户名或密码错误");
}
}
/**
* 退出登录
* @param request
* @return
*/
@GetMapping("logout")
public Result logout(HttpServletRequest request){
redisTemplate.delete(RedisConst.USER_LOGIN_KEY_PREFIX + request.getHeader("token"));
return Result.ok();
}
}
(3)配置网关server-gateway
- id: service-user
uri: lb://service-user
predicates:
- Path=/*/user/**
- id: web-passport
uri: lb://web-all
predicates:
- Host=passport.gmall.com
(4)在web-all模块添加实现
在web-all 项目中跳转页面
package com.atguigu.gmall.all.controller;
/**
* <p>
* 用户认证接口
* </p>
*
*/
@Controller
public class PassportController {
/**
*
* @return
*/
@GetMapping("login.html")
public String login(HttpServletRequest request) {
String originUrl = request.getParameter("originUrl");
request.setAttribute("originUrl",originUrl);
return "login";
}
}
登录页面
页面资源: \templates\login1.html
login1.html 没有公共头部信息
login.html 有公共头部信息
Html:Login代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE">
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<title>尚品汇,欢迎登录</title>
<link rel="icon" href="./img/favicon.ico">
<link rel="stylesheet" type="text/css" href="/css/all.css" />
<link rel="stylesheet" type="text/css" href="/css/pages-login.css" />
<div th:include="common/head :: head"></div>
</head>
<body>
<!-- 头部栏位 -->
<!--页面顶部-->
<div th:include="common/loginHeader :: loginHeader"></div>
<div class="login-box">
<!--loginArea-->
<div class="loginArea">
<div class="py-container login">
<div class="loginform">
<ul class="sui-nav nav-tabs tab-wraped">
<li id="tabOtherLogin" onclick="switchOtherLogin()">
<a href="javascript:void(0)" data-toggle="tab" >
<h3>扫描登录</h3>
</a>
</li>
<li id="tabSuiForm" class="active" onclick="switchSuiForm()">
<a href="javascript:void(0)" data-toggle="tab" >
<h3>账户登录</h3>
</a>
</li>
</ul>
<div class="tab-content tab-wraped">
<div id="index" class="tab-pane ">
<p>二维码登录,暂为官网二维码</p>
<img src="./img/wx_cz.jpg" />
</div>
<div id="profile" class="tab-pane active">
<form class="sui-form">
<div class="input-prepend"><span class="add-on loginname"></span>
<input id="inputName" type="text" v-model="user.loginName" placeholder="邮箱/用户名/手机号" class="span2 input-xfat">
</div>
<div class="input-prepend"><span class="add-on loginpwd"></span>
<input id="inputPassword" type="password" v-model="user.passwd" placeholder="请输入密码" class="span2 input-xfat">
</div>
<div class="setting">
<label class="checkbox inline">
<input name="m1" type="checkbox" value="2" checked="">
自动登录
</label>
<span class="forget">忘记密码?</span>
</div>
<div class="logined">
<a class="sui-btn btn-block btn-xlarge btn-danger" href="javascript:" @click="submitLogin()">登 录</a>
</div>
</form>
<div class="otherlogin">
<div class="types">
<ul>
<li><img src="./img/qq.png" width="35px" height="35px" /></li>
<li><img src="./img/sina.png" /></li>
<li><img src="./img/ali.png" /></li>
<li><img src="./img/weixin.png" /></li>
</ul>
</div>
<span class="register"><a href="register.html" target="_blank">立即注册</a></span>
</div>
</div>
</div>
</div>
</div>
</div>
<!--foot-->
<div class="py-container copyright">
<ul>
<li>关于我们</li>
<li>联系我们</li>
<li>联系客服</li>
<li>商家入驻</li>
<li>营销中心</li>
<li>手机尚品汇</li>
<li>销售联盟</li>
<li>尚品汇社区</li>
</ul>
<div class="address">地址:北京市昌平区宏福科技园综合楼6层</div>
<div class="beian">京ICP备19006430号
</div>
</div>
</div>
<!--页面底部END-->
<script src="/js/api/login.js"></script>
<script th:inline="javascript">
var item = new Vue({
el: '#profile',
data: {
originUrl: [[${originUrl}]],// 后台存储这么一个originUrl
user: {
loginName: '',
passwd: ''
}
},
created() {
},
methods: {
submitLogin() {
/*后台就可以使用UserInfo 对象接收*/
login.login(this.user).then(response => {
// debugger
if (response.data.code == 200) {
// 把token存在cookie中、也可以放在localStorage中
// result.ok(map);
// response 相当于Result
// response.data 相当于 Result.ok();
// response.data.data data看做一个map 相当于 Result.ok(data);
// response.data.map.token 相当于 result.ok(map)
// map 中有token 同时还有 nickName
auth.setToken(response.data.data.token)
// response.data.data 相当于返回来的map
// JSON.stringify(response.data.data) 将map 数据变为json对象,存储到cookie 中
auth.setUserInfo(JSON.stringify(response.data.data))
// 输入日志:是否有 originUrl 回跳url!
// originUrl = 记录用户在哪点击的登录url ,当用户登录成功之后,又跳转到了原来的url!
console.log("originUrl:"+this.originUrl);
if(this.originUrl == ''){
window.location.href="http://www.gmall.com/index.html"
return ;
} else {
window.location.href = decodeURIComponent(this.originUrl)
}
} else {
alert(response.data.data.message)
}
})
}
}
})
</script>
</body>
</html>
提取头部信息
提取头部信息我们会用到thymeleaf 两个标签:
th:fragment:定义代码块
th:include:将代码块片段包含的内容插入到使用了th:include的HTML标签中
1,定义头部代码块(/common/header.html),关键代码
<div id="nav-bottom" th:fragment="header">
<!--顶部-->
<div class="nav-top" id="header">
...
</div>
</div>
2,在其他页面引用头部代码块
<div th:include="common/header :: header"></div>
头部登录状态处理
思路:登录成功后我们将用户信息写入了cookie,所以我们判断cookie中是否有用户信息,如果有则显示登录用户信息和退出按钮,我们采取vue的渲染方式
关键代码
Header.html 中
<ul class="fl">
<li class="f-item">尚品汇欢迎您!</li>
<li v-if="userInfo.nickName == ''" class="f-item">请<span><a href="javascript:" @click="login()">登录</a></span> <span><a href="#">免费注册</a></span></li>
<li v-if="userInfo.nickName != ''" class="f-item"><span>{{userInfo.nickName}}</span> <span><a href="javascript:" @click="logout()">退出</a></span></li>
</ul>
<script type="text/javascript" src="/js/plugins/jquery/jquery.min.js"></script>
<script type="text/javascript" src="/js/plugins/jquery.cookie.js"></script>
<script src="/js/plugins/vue.js"></script>
<script src="/js/plugins/axios.js"></script>
<script src="/js/auth.js"></script>
<script src="/js/request.js"></script>
<script src="/js/api/login.js"></script>
<script th:inline="javascript">
var item = new Vue({
el: '#header',
data: {
userInfo: {
nickName: '',
name: ''
}
},
created() {
this.showInfo()
},
methods: {
showInfo() {
// debugger
if(auth.getUserInfo()) {
this.userInfo = auth.getUserInfo()
console.log("--------"+this.userInfo.nickName)
}
},
logout() {
//debugger
login.logout().then(response => {
console.log("已退出")
auth.removeToken()
auth.removeUserInfo()
//跳转页面
window.location.href = "/"
})
}
}
})
</script>
头部关键字搜索
退出接口: