目录
一、用户直接点击登录功能登录
1、访问首页
首页“登录”选项,是一个a标签,会携带返回地址信息(当前页面的url)
直接登录的返回地址即为当前页面
其中:http://localhost:8085/index为登录页面;http://localhost:8083/index为首页
2、点击“你好,请登录”跳转到登录页面
进入到登录页面后,会获取到地址栏中的ReturnUrl并保存到ModelMap中
在此页面中放置属性为ReturnUrl的input标签并隐藏,点击登陆后携带返回地址
此时地址栏中的地址即为上述href指向的地址:
点击登录按钮,会调用login接口,通过token的值决定接下来的操作(login接口主要用户验证用户名和密码)
此时ReturnUrl=http://localhost:8003/index
3、点击登录按钮
步骤二中的调用login方法,返回token后跳转到指定ReturnUrl
至此,整个主动登录的流程介绍完毕~
二、结算时被登录功能拦截
1、点击去结算
该功能必须登录后才能完成
2、拦截器拦截,并跳转到登陆页面
地址栏携带:ReturnUrl=http://localhost:8084/toTrade
那么,拦截器是如何拦截的呢?
3、拦截器
(1)AuthInterceptor
package com.atguigu.gmall.interceptors;
import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.annotations.LoginRequired;
import com.atguigu.gmall.util.CookieUtil;
import com.atguigu.gmall.util.HttpclientUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.invoke.MethodHandle;
import java.util.HashMap;
import java.util.Map;
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 拦截代码
// 判断被拦截的请求的访问的方法的注解(是否时需要拦截的)
HandlerMethod hm = (HandlerMethod) handler;
LoginRequired methodAnnotation = hm.getMethodAnnotation(LoginRequired.class);
StringBuffer url = request.getRequestURL();
System.out.println(url);
// 是否拦截
if (methodAnnotation == null) {
return true;
}
String token = "";
String oldToken = CookieUtil.getCookieValue(request, "oldToken", true);
if (StringUtils.isNotBlank(oldToken)) {
token = oldToken;
}
String newToken = request.getParameter("token");
if (StringUtils.isNotBlank(newToken)) {
token = newToken;
}
// 是否必须登录
boolean loginSuccess = methodAnnotation.loginSuccess();// 获得该请求是否必登录成功
// 调用认证中心进行验证
String success = "fail";
Map<String,String> successMap = new HashMap<>();
if(StringUtils.isNotBlank(token)){
String ip = request.getHeader("x-forwarded-for");// 通过nginx转发的客户端ip
if(StringUtils.isBlank(ip)){
ip = request.getRemoteAddr();// 从request中获取ip
if(StringUtils.isBlank(ip)){
ip = "127.0.0.1";
}
}
String successJson = HttpclientUtil.doGet("http://localhost:8085/verify?token=" + token+"¤tIp="+ip);
successMap = JSON.parseObject(successJson,Map.class);
success = successMap.get("status");
}
if (loginSuccess) {
// 必须登录成功才能使用
if (!success.equals("success")) {
//重定向会passport登录
StringBuffer requestURL = request.getRequestURL();
response.sendRedirect("http://localhost:8085/index?ReturnUrl="+requestURL);
return false;
}
// 需要将token携带的用户信息写入
request.setAttribute("memberId", successMap.get("memberId"));
request.setAttribute("nickname", successMap.get("nickname"));
//验证通过,覆盖cookie中的token
if(StringUtils.isNotBlank(token)){
CookieUtil.setCookie(request,response,"oldToken",token,60*60*2,true);
}
} else {
// 没有登录也能用,但是必须验证
if (success.equals("success")) {
// 需要将token携带的用户信息写入
request.setAttribute("memberId", successMap.get("memberId"));
request.setAttribute("nickname", successMap.get("nickname"));
//验证通过,覆盖cookie中的token
if(StringUtils.isNotBlank(token)){
CookieUtil.setCookie(request,response,"oldToken",token,60*60*2,true);
}
}
}
return true;
}
}
拦截器需要判断注解:是否需要进行身份验证、是否必须通过身份验证
|
老token空 |
老token不空 |
新token空 |
从未登录过 |
之前登录过 |
新token不空 |
刚刚登录 |
过期 |
(2)WebMvcConfiguration
package com.atguigu.gmall.config;
import com.atguigu.gmall.interceptors.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {//类似于xml配置文件
@Autowired
AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
//表示拦截所有请求
//排除/error
registry.addInterceptor(authInterceptor).addPathPatterns("/**").excludePathPatterns("/error");
super.addInterceptors(registry);
}
}
(3)LoginRequired
package com.atguigu.gmall.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) //表示只在方法上有效
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
boolean loginSuccess() default true;
}
除了可以通过web模块是否扫描拦截器来决定拦截器的使用之外
还可以通过注解的方式来标识具体的方法是否需要通过拦截器
@LoginRequired
第一类方法:不需要进行拦截的方法(没有拦截器注解),直接放行,不加@LoginRequired
第二类方法:需要拦截但是拦截校验失败(用户没登陆或者登录过期了)也可以继续访问的方法,比如说购物车中的所有方法@LoginRequired(loginSuccess=false)
第三类方法:需要拦截,并且拦截校验一定要通过(用户登录成功了)才能访问的方法@LoginRequired(loginSuccess=true)
(4)CookieUtil
package com.atguigu.gmall.util;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
/**
* @param
* @return
*/
public class CookieUtil {
/***
* 获得cookie中的值,默认为主ip:www.gmall.com
* @param request
* @param cookieName
* @param isDecoder
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
Cookie[] cookies = request.getCookies();
if (cookies == null || cookieName == null){
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals(cookieName)) {
if (isDecoder) {//如果涉及中文
retValue = URLDecoder.decode(cookies[i].getValue(), "UTF-8");
} else {
retValue = cookies[i].getValue();
}
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return retValue;
}
/***
* 设置cookie的值
* @param request
* @param response
* @param cookieName
* @param cookieValue
* @param cookieMaxage
* @param isEncode
*/
public static void setCookie(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
cookie.setDomain(getDomainName(request));
// 在域名的根路径下保存
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/***
* 获得cookie的主域名,本系统为gmall.com,保存时使用
* @param request
* @return
*/
private static final String getDomainName(HttpServletRequest request) {
String domainName = null;
String serverName = request.getRequestURL().toString();// 获得浏览器地址栏的url
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];
}
System.out.println("domainName = " + domainName);
return domainName;
}
/***
* 将cookie中的内容按照key删除
* @param request
* @param response
* @param cookieName
*/
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
setCookie(request, response, cookieName, null, 0, false);
}
}
(5)JwtUtil
package com.atguigu.gmall.util;
import io.jsonwebtoken.*;
import java.util.Map;
public class JwtUtil {
public static String encode(String key, Map<String,Object> param, String salt){
if(salt!=null){
key+=salt;
}
JwtBuilder jwtBuilder = Jwts.builder().signWith(SignatureAlgorithm.HS256,key);
jwtBuilder = jwtBuilder.setClaims(param);
String token = jwtBuilder.compact();
return token;
}
public static Map<String,Object> decode(String token ,String key,String salt){
Claims claims=null;
if (salt!=null){
key+=salt;
}
try {
claims= Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
} catch ( JwtException e) {
return null;
}
return claims;
}
}
TestJwt
package com.atguigu.gmall.util;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.impl.Base64UrlCodec;
import org.apache.commons.lang3.StringUtils;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class TestJwt {
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
map.put("memberId","1");
map.put("nickname","zhangsan");
String ip = "127.0.0.1";
String time = new SimpleDateFormat("yyyyMMdd HHmm").format(new Date());
String encode = JwtUtil.encode("2019gmall0105", map, ip + time);
System.err.println(encode);
// String tokenUserInfo = StringUtils.substringBetween(encode, ".");
Base64UrlCodec base64UrlCodec = new Base64UrlCodec();
byte[] tokenBytes = base64UrlCodec.decode("eyJuaWNrbmFtZSI6InpoYW5nc2FuIiwibWVtYmVySWQiOiIxIn0");
String tokenJson = null;
try {
tokenJson = new String(tokenBytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Map map1 = JSON.parseObject(tokenJson, Map.class);
System.out.println("64="+map1);
}
}
(6)HttpclientUtil
package com.atguigu.gmall.util;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpclientUtil {
public static String doGet(String url) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
// 创建http GET请求
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response = null;
try {
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity, "UTF-8");
EntityUtils.consume(entity);
httpclient.close();
return result;
}
httpclient.close();
}catch (IOException e){
e.printStackTrace();
return null;
}
return null;
}
public static String doPost(String url, Map<String,String> paramMap) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
// 创建http Post请求
HttpPost httpPost = new HttpPost(url);
CloseableHttpResponse response = null;
try {
List<BasicNameValuePair> list=new ArrayList<>();
for (Map.Entry<String, String> entry : paramMap.entrySet()) {
list.add(new BasicNameValuePair(entry.getKey(),entry.getValue())) ;
}
HttpEntity httpEntity=new UrlEncodedFormEntity(list,"utf-8");
httpPost.setEntity(httpEntity);
// 执行请求
response = httpclient.execute(httpPost);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity, "UTF-8");
EntityUtils.consume(entity);
httpclient.close();
return result;
}
httpclient.close();
}catch (IOException e){
e.printStackTrace();
return null;
}
return null;
}
}
引入依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
三、具体登录验证代码实现
1、PasswordController
@Controller
public class PasswordController {
@Reference
private UserService userService;
@RequestMapping("index")
public String index(String ReturnUrl, ModelMap map){
map.put("ReturnUrl",ReturnUrl);
return "index";
}
@RequestMapping("login")
@ResponseBody
public String login(UmsMember umsMember, HttpServletRequest request){
String token = "";
// 调用用户服务验证用户名和密码
UmsMember umsMemberLogin = userService.login(umsMember);
if(umsMemberLogin!=null){
// 登录成功
// 用jwt制作token
String memberId = umsMemberLogin.getId();
String nickname = umsMemberLogin.getNickname();
Map<String,Object> userMap = new HashMap<>();
userMap.put("memberId",memberId);
userMap.put("nickname",nickname);
String ip = request.getHeader("x-forwarded-for");// 通过nginx转发的客户端ip
if(StringUtils.isBlank(ip)){
ip = request.getRemoteAddr();// 从request中获取ip
if(StringUtils.isBlank(ip)){
ip = "127.0.0.1";
}
}
// 按照设计的算法对参数进行加密后,生成token
token = JwtUtil.encode("2019gmall0105", userMap, ip);
// 将token存入redis一份
userService.addUserToken(token,memberId);
}else{
// 登录失败
token = "fail";
}
return token;
}
@RequestMapping("verify")
@ResponseBody
public String verify(String token,String currentIp){
// 通过jwt校验token真假
Map<String,String> map = new HashMap<>();
Map<String, Object> decode = JwtUtil.decode(token, "2019gmall0105", currentIp);
if(decode!=null){
map.put("status","success");
map.put("memberId",(String)decode.get("memberId"));
map.put("nickname",(String)decode.get("nickname"));
}else{
map.put("status","fail");
}
return JSON.toJSONString(map);
}
}
2、UserServiceImpl
@Override
public UmsMember login(UmsMember umsMember) {
Jedis jedis = null;
try {
jedis = redisUtil.getJedis();
if (jedis != null) {
String umsMemberStr = jedis.get("user:" + umsMember.getPassword() + ":info");
if (StringUtils.isNotBlank(umsMemberStr)) {
//密码正确
UmsMember umsMemberFromCache = JSON.parseObject(umsMemberStr, UmsMember.class);
return umsMemberFromCache;
}
}
//连接redis失败或者缓存中密码不正确的时候都会执行下面的
//开启数据库
UmsMember umsMemberFromDb = loginFromDb(umsMember);
if (umsMemberFromDb != null) {
jedis.setex("user:" + umsMemberFromDb.getPassword() + ":info", 60 * 60 * 24, JSON.toJSONString(umsMemberFromDb));
}
return umsMemberFromDb;
}finally {
jedis.close();
}
}
@Override
public void addUserToken(String token, String memberId) {
Jedis jedis = redisUtil.getJedis();
jedis.setex("user:"+memberId+":token",60 * 60 * 2,token);
jedis.close();
}
private UmsMember loginFromDb(UmsMember umsMember) {
List<UmsMember> umsMembers = userMapper.select(umsMember);
if(umsMembers!=null){
return umsMembers.get(0);
}
return null;
}