业务流程,
1,登陆
@Override
public UserInfo login(UserInfo userInfo) {
// 页面传递 admin -- 123
// db admin -- 202cb962ac59075b964b07152d234b70
// 将 123 加密
String passwd = userInfo.getPasswd();
// 123 -- 202cb962ac59075b964b07152d234b70
String newPwd = DigestUtils.md5DigestAsHex(passwd.getBytes());
userInfo.setPasswd(newPwd);
UserInfo info = userInfoMapper.selectOne(userInfo);
// 如果用户登录成功则将用户信息存放到redis
Jedis jedis = redisUtil.getJedis();
if (info!=null){
// 定义key:user:1:info
String userKey = userKey_prefix+info.getId()+userinfoKey_suffix;
// 做存储
jedis.setex(userKey,userKey_timeOut, JSON.toJSONString(info));
jedis.close();
return info;
}
return null;
}
2,带token跳转原web应用页面(用户登录成功之后返回token)京东使用JWT生成token,
1. cookie 中存放 token{认为是一个值} 地下城勇士 DNF: token = 门票:
token === JWT {公钥+私钥+签名}最后使用base64编码得到JWT。
(1、 公共部分
主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。
2、 私有部分
用户自定义的内容,根据实际需要真正要封装的信息。
3、 签名部分
根据用户信息+盐值+密钥生成的签名。如果想知道JWT是否是真实的只要把JWT的信息取出来,加上盐值和服务器中的密钥就可以验证真伪。所以不管由谁保存JWT,只要没有密钥就无法伪造。
用户信息+ip=密钥:
iP:当前服务器的Ip地址!
4、 base64编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以吧base64编码解成明文,所以不要在JWT中放入涉及私密的信息,因为实际上JWT并不是加密信息。
)
2. token 是在登录成功之后创建的。
3. 有了token 可以访问项目。
是因为token 中有私钥{用户信息 -- token中的用户名密码[通过解密] 和 redis 做比较}
4. 电商哪里需要登录 结算,其他地方不需要登录。拦截器 -- springmvc。
生成token用到的jwt工具类,主要有decode解析,encode加密
@RequestMapping(value = "login")
@ResponseBody
public String login(UserInfo userInfo, HttpServletRequest request){
// 获取linux服务器的ip=192.168.67.1,反正就是获取ip,
String ip = request.getHeader("X-forwarded-for");
// 用户名+密码进行验证 select * from user_info where uname = ? and pwd = ?
UserInfo loginUser = userInfoService.login(userInfo);
if (loginUser!=null){
// 做token -- JWT
HashMap<String, Object> map = new HashMap<>();
map.put("userId",loginUser.getId());
map.put("nickName",loginUser.getNickName());
String token = JwtUtil.encode(key, map, ip);//加密 随后会进行解密 具体看JWTutil.calss
return token;
}else {
return "fail";
}
}
下面步骤,通过解析token字符串,的userId,通过id去redis 中进行查询,如果redis中有当前用户,则认证成功!否则fail!
为啥要做认证?为啥要做认证?为啥要做认证?下面就是认证!!
sso—一处登录,多出使用,其他模块只需要调用认证方法!
(说白了就是 到了解析信息 和 redis做比较了!)
感觉不如图片好看呢?
@Override
public UserInfo verify(String userId) {
// 根据userId redis 中查数据
Jedis jedis = redisUtil.getJedis();
// 定义key
String key = userKey_prefix+userId+userinfoKey_suffix;
// 通过key 获取数据
String userJson = jedis.get(key);
// 因为认证操作相当于其他模块 ,在登录,就需要延长用户过期时间
jedis.expire(key,userKey_timeOut);
if (userJson!=null && userJson.length()>0){
// 将字符串转换为对象
UserInfo userInfo = JSON.parseObject(userJson, UserInfo.class);
return userInfo;
}
return null;
}
以上就是 加密 解析 对比redis 对应的上 就将过期时间 延迟。表明用户还在使用啊。如果很长时间 没更新这个redis过期时间,表明 用户可能 出去了,不再使用了,自然 将这个key删除啊!
实现 登陆----认证-----
接下来 将是重中之重!
三、 业务模块页面登录情况检查
问题:
1 、由认证中心签发的token如何保存? Cookie
2 、难道每一个模块都要做一个token的保存功能?gmall-web-util
(在gmall-web-util项目登录成功后写入cookie。)
3 、如何区分请求是否一定要登录? 自定义注解
package com.atguigu.gmall0416.config;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.impl.Base64UrlCodec;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
// 实现,继承 被spring 容器扫描
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
// preHandle 进入控制器之前。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 将生产token 放入cookie 中
// http://item.gmall.com/32.html?newToken=eyJhbGciOiJIUzI1NiJ9.eyJuaWNrTmFtZSI6IkFkbWluaXN0cmF0b3IiLCJ1c2VySWQiOiIyIn0.WUvbFvXQnTMBGNyHWT-DE41MR9cn7c_W1oAtDAzb7VU
String token = request.getParameter("newToken");
if (token!=null){
// 将token 放入cookie 中
CookieUtil.setCookie(request,response,"token",token,WebConst.COOKIE_MAXAGE,false);
}
// 直接访问登录页面,当用户进入其他项目模块中。
if (token==null){
// 如果用户登录了,访问其他页面的时候不会有newToken,那么token 可能已经在cookie 中存在了
token = CookieUtil.getCookieValue(request,"token",false);
}
// 已经登录的token,cookie 中的token。
if (token!=null){
// 去token 中的是有效数据,解密
Map map = getUserMapByToken(token);
String nickName = (String) map.get("nickName");
request.setAttribute("nickName", nickName);
}
//这儿 !!是cookie中没有token,需要再去认证
// Object handler
// 获取方法,获取方法上的注解 判断时候有LoginRequire注解?干啥呢?
HandlerMethod handlerMethod = (HandlerMethod) handler;
LoginRequire methodAnnotation = handlerMethod.getMethodAnnotation(LoginRequire.class);
// 说明类上有注解!
if (methodAnnotation!=null){
// 必须要登录【调用认证】
String remoteAddr = request.getHeader("x-forwarded-for");
// 认证控制器在那个项目? 远程调用,
String result = HttpClientUtil.doGet(WebConst.VERIFY_ADDRESS + "?token=" + token + "¤tIp=" + remoteAddr);
if ("success".equals(result)){
// 说明当前用户已经登录,保存userId : 购物车使用!
Map map = getUserMapByToken(token);
String userId = (String) map.get("userId");
request.setAttribute("userId", userId);
return true;
} else {
// fail
if (methodAnnotation.autoRedirect()){
// 认证失败!重新登录!
/*http://passport.atguigu.com/index?originUrl=http%3A%2F%2Fitem.gmall.com%2F32.html*/
String requestURL = request.getRequestURL().toString(); // http://item.gmall.com/28.html
// 进行加密编码
String encodeURL = URLEncoder.encode(requestURL, "UTF-8");
response.sendRedirect(WebConst.LOGIN_ADDRESS+"?originUrl="+encodeURL);
return false;
}
}
}
return true;
}
// 解密
private Map getUserMapByToken(String token) {
// eyJhbGciOiJIUzI1NiJ9.eyJuaWNrTmFtZSI6Im1hcnJ5IiwidXNlcklkIjoiMTAwMSJ9.TF1RTg_1TnkPNOAkA4Gq549iqwzsBplgeabpHvW15ng
String tokenUserInfo = StringUtils.substringBetween(token, ".");
// 使用了另一个类
Base64UrlCodec base64UrlCodec = new Base64UrlCodec();
byte[] bytes = base64UrlCodec.decode(tokenUserInfo);
// 数组-- map
// 字符串
String str = null;
try {
str = new String(bytes ,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 字符串--map
Map map = JSON.parseObject(str, Map.class);
return map;
}
}
下面是添加拦截器的配置
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
@Autowired
AuthInterceptor authInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
/*拦截所有请求,也就是在所有的请求之前 都要进行 authInterceptor类方法 */
registry.addInterceptor(authInterceptor).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
最后 自定义个注解。
@Target(ElementType.METHOD)//作用在什么地方
@Retention(RetentionPolicy.RUNTIME) //生命周期!
public @interface LoginRequire {
// 默认设置
boolean autoRedirect() default true;
}
里面有httpCilentUtil,cookieUtil,拦截器配置,非常的方便 对远程调用 也就是跨工程调用。还有就是 更好的操作 cookie 中的 token。还有拦截器配置,给每一个controller之前都添加 一个方法,通过一个自定义的注解 进行 判断,是否在方法上添加了这个注解,从而确定 是不是 要去认证!