Token方式 验证登录
Token 验证
在这之前先简单讲下登陆中验证码认证过程,先是在WebSecurityConfig里面配置下生成验证码的路径,使用谷歌的com.google.code.kaptcha API中的Producer对象生成验证码,后台生成Cookie 对象,key可以自定义,值用uuid去生成,然后把验证码存到redis中(key用的刚才uuid生成的那个值,value存验证码),然后把Cookie 返给前端。登录的时候传验证码过来,并且把刚才返的Cookie带过来,校验就根据cookie从redis中去取验证码。
public void captcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setHeader(“Cache-Control”, “no-store, no-cache”);
response.setContentType(“image/jpeg”);
//生成文字验证码String text = producer.createText();//生成图片验证码BufferedImage image = producer.createImage(text);//保存sessionString uuid = UUID.randomUUID().toString();Cookie cookie = new Cookie(SystemConstants.KAPTCHA_SESSION_KEY, uuid);response.addCookie(cookie);redisUtils.set(uuid, text);ServletOutputStream out = response.getOutputStream();ImageIO.write(image, "jpg", out);
}
使用基于 Token 的身份验证方法,大概的流程是这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
总的来说就是客户端在首次登陆以后,服务端再次接收http请求的时候,就只认token了,请求只要每次把token带上就行了,服务器端会拦截所有的请求,然后校验token的合法性,合法就放行,不合法就返回401(鉴权失败)。
乍的一看好像和前面的seesion-cookie有点像,seesion-cookie是通过seesionid来作为浏览器和服务端的链接桥梁,而token验证方式貌似是token来起到seesionid的角色。其实这两者差别是很大的。
- sessionid 他只是一个唯一标识的字符串,服务端是根据这个字符串,来查询在服务器端保持的seesion,这里面才保存着用户的登陆状态。但是token本身就是一种登陆成功凭证,他是在登陆成功后根据某种规则生成的一种信息凭证,他里面本身就保存着用户的登陆状态。服务器端只需要根据定义的规则校验这个token是否合法就行。
- session-cookie是需要cookie配合的,居然要cookie,那么在http代理客户端的选择上就是只有浏览器了,因为只有浏览器才会去解析请求响应头里面的cookie,然后每次请求再默认带上该域名下的cookie。但是我们知道http代理客户端不只有浏览器,还有原生APP等等,这个时候cookie是不起作用的,或者浏览器端是可以禁止cookie的(虽然可以,但是这基本上是属于吃饱没事干的人干的事)…,但是token 就不一样,他是登陆请求在登陆成功后再请求响应体中返回的信息,客户端在收到响应的时候,可以把他存在本地的cookie,storage,或者内存中,然后再下一次请求的请求头重带上这个token就行了。简单点来说cookie-session机制他限制了客户端的类型,而token验证机制丰富了客户端类型。
- 时效性。session-cookie的sessionid实在登陆的时候生成的而且在登出事时一直不变的,在一定程度上安全就会低,而token是可以在一段时间内动态改变的。
- 可扩展性。token验证本身是比较灵活的,一是token的解决方案有许多,常用的是JWT,二来我们可以基于token验证机制,专门做一个鉴权服务,用它向多个服务的请求进行统一鉴权。
下面就拿最常用的JWT(JSON WEB TOKEN)来说:
JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案,就是登陆成功后将相关信息组成json对象,然后对这个对象进行某中方式的加密,返回给客户端,客户端在下次请求时带上这个token,服务端再收到请求时校验token合法性,其实也就是在校验请求的合法性。
具体实现如下:
调用登录接口,先是进入拦截器匹配url,
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {String servletPath = request.getServletPath();boolean isLogin= pathMatcher.match("/login", servletPath);if (isNoPermission(servletPath,"/swagger-ui.html","/swagger-resources/**","/v2/**", "/webjars/**","/api/voucher/download/**","/project/sourceAttach/download/**")){filterChain.doFilter(request, response);return;}if(!isLogin){boolean isCaptcha=pathMatcher.match("/code/**", servletPath);if(!isCaptcha){validToken(request,response);}}else{/*** 验证码校验*/if (!"dev".equals(environment)){validKaptcha(request);}}}catch (BadCredentialsException e){authenticationFailureHandler.onAuthenticationFailure(request,response,e);return;}filterChain.doFilter(request, response);}
通过后就进行调用登陆器
/**
自定义登录登录器,使用JSON登录
*/
public class LoginAuthFilter extends UsernamePasswordAuthenticationFilter {private static final Logger LOGGER = LoggerFactory.getLogger(LoginAuthFilter.class);
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if ( !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}String username = this.obtainUsername(request);String password = this.obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);this.setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);
}
接着进行登录认证
/**
登录认证逻辑
*/
public class LoginAuthenticationManager implements AuthenticationManager {private static final Logger LOGGER = LoggerFactory.getLogger(LoginAuthenticationManager.class);
private UserService userService;
public LoginAuthenticationManager(UserService userService){
this.userService=userService;
}@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {if(authentication.getPrincipal() == null || authentication.getCredentials() == null) {throw new BadCredentialsException("登陆验证失败,用户名或密码为空");}String username = (String)authentication.getPrincipal();//查询用户信息User user=userService.loadUserByUsername(username);if(user==null){throw new BadCredentialsException("账号信息不存在");}String password = (String)authentication.getCredentials();Boolean passwordTrue=new BCryptPasswordEncoder().matches(password,user.getPassword());//比较密码是否一致if(!passwordTrue){throw new BadCredentialsException("密码不正确");}return new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(), user.getAuthorities());
}
}
认证通过后就进入了自定义的 登录认证成功的handler进行生成token操作,并把token存进redis,然后返回给前端,这里还可以把用户相关信息封装好根据需要返给前端
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
User sysUserEntity=(User) authentication.getPrincipal();User sysUser=new User();sysUser.setUserId(sysUserEntity.getUserId());sysUser.setUserName(sysUserEntity.getUserName());sysUser.setLoginUserName(sysUserEntity.getLoginUserName());//创建tokenString token= JwtTokenUtil.createJWT(sysUserEntity);redisUtils.set(token+sysUserEntity.getUserId(),token);//设置一下用户登录信息到redis中去redisUtils.set(sysUserEntity.getUserId(), JSON.toJSONString(sysUserEntity));JSONObject data = new JSONObject();data.put("code", SystemConstants.SUCCESS);data.put("msg", "登陆成功");data.put("data", sysUser);httpServletResponse.setHeader(SystemConstants.AUTHORIZATION,token);response(httpServletResponse, data);
}
对应的生成token逻辑,这里使用jwt生成的
/**
* 构建jwt
* @param user
* @param
* @return
*/
public static String createJWT(User user) {
try {
// 使用HS256加密算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);//生成签名密钥byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(BASE64_BIARY);Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());//添加构成JWT的参数JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")// 可以将基本不重要的对象信息放到claims.setSubject(user.getUserName()) // 代表这个JWT的主体,即它的所有人.setIssuedAt(new Date()) // 是一个时间戳,代表这个JWT的签发时间;.setClaims(accountEntityfillMap(user)).signWith(signatureAlgorithm, signingKey);//生成JWTreturn builder.compact();} catch (Exception e) {log.error("签名失败", e);throw e;}
}public static Map<String, Object> accountEntityfillMap(User user) throws NullPointerException {Map<String, Object> info = new WeakHashMap<>();info.put("loginUserName", user.getLoginUserName());info.put("userId", user.getUserId());info.put("exTime",new Date());return info;
}
然后前端把登陆成功返的token存起来,后面每次调用接口的时候带上就可以了,调用接口的时候会先进拦截器,然后就是校验token逻辑
private void validToken(HttpServletRequest request, HttpServletResponse response) throws BadCredentialsException{
boolean hasPower = false;//获取请求头String token=request.getHeader(SystemConstants.AUTHORIZATION);if(ObjectMapper==null){ObjectMapper = new ObjectMapper();}try {if(token ==null){throw new BadCredentialsException("token is empty");}//解析tokenClaims claims=JwtTokenUtil.parseJWT(token);String userId=claims.get("userId",String.class);if (userId == null || SecurityContextHolder.getContext().getAuthentication() == null) {//判断是否过期if(redisUtils.get(userId)==null){throw new BadCredentialsException("token 已过期");}//获取redis中的用户信息String userJson=redisUtils.get(userId);User user = JSON.parseObject(userJson,User.class);//判断权限if(user!=null && CollectionUtils.isEmpty(user.getPermissions())){throw new BadCredentialsException("当前请求没有权限,请联系管理员");}if(!hasPower && user.getPermissions() != null) {for (Permission ga : user.getPermissions()) {AntPathRequestMatcher matcher = new AntPathRequestMatcher(ga.getUri());if (hasPower=matcher.matches(request)) {break;}}}if(!hasPower) { //当前请求没有权限throw new BadCredentialsException("当前请求没有权限,请联系管理员");}UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));//设置为已登录SecurityContextHolder.getContext().setAuthentication(authentication);}}catch (BadCredentialsException e){log.error("拦截token验证", e);// ExceptionPrinterUtil.instance().write(response, e.getMessage(), "UTF-8");throw new BadCredentialsException(e.getMessage());}
}
/**
* 解析jwt
* @param jsonWebToken
* @param
* @return
*/
public static Claims parseJWT(String jsonWebToken) {
try {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(BASE64_BIARY))
.parseClaimsJws(jsonWebToken).getBody();
return claims;
} catch (ExpiredJwtException eje) {
log.error("===== Token过期 =", eje);
throw eje;
} catch (Exception e){
log.error("= token解析异常 =====", e);
throw e;
}
}
token校验通过后,就可以访问具体接口,请求数据,并返给前端
Token方式 验证登录相关推荐
- 配置Open***使用User/Pass方式验证登录
Open***和PPTP ***相比存在诸多的优势,最明显的是Open***支持NAT穿越,也就是说在nat环境下使用open***只需要一个在路由器上做一个端口映射即可!不需要其他路由的支持,要知道 ...
- 微信小程序python token验证_微信小程序登录对接Django后端实现JWT方式验证登录
点击授权按钮后可以显示部分资料和头像,点击修改资料可以修改部分资料. 1.使用微信小程序登录和获取用户信息Api接口 2.把Api获取的用户资料和code发送给django后端 3.通过微信接口把co ...
- java 用户登录token_Java,SpringBoot采用token方式实现登录认证
IT小奋斗2021-01-13 21:48:33 Token,令牌,访问资源的凭证,每次访问带上这个令牌,就可识别出用户身份. JWT (JsonWebToken),是实现token技术的一种解决方案 ...
- 数据采集-关于post生成ticket传递参数给另一个页面用get方式验证登录
1.先输入错误密码看提交方式: 没有postform 而且还是get方式传输 有点奇怪 2.看源代码 可以看到登录页嵌入iframe 通过里面的链接传给另一个页面,点击 3,同第一步 可以看出 查看源 ...
- 二、SpringSecurity 自定义手机验证登录方式
简介 在上一篇文章中,我们介绍了如何搭建一套基于SpringSecuity的项目框架,并且进行了演示,本文将继续扩展项目功能,实现自定义用户登录功能. 项目源码仓库:Gitee 代码分支:lesson ...
- 解读APP新一代验证登录方式——一键登录/免密认证
近年来,验证领域迎来一个黑科技--用户在APP上进行账号注册或者号码绑定时,不需要接收短信验证码,直接可以以本机号码实现秒级验证.这种新颖且便捷的验证方式称为"一键认证". 一键认 ...
- 使用token验证登录信息,把token存到数据库中
使用token验证登录信息,把生成的token存到数据库中,根据用户id判断是否重复登录,重复登录就重置到期时间 调用登录接口返回出token和uuid信息 每次访问都需要在Headers里面添加该数 ...
- 交换机配置ssh密码验证登录方式
交换机配置ssh密码验证登录方式 一.背景: 1.由于PC机串口不支持热插拔,请不要在交换机带电的情况下,将串口插入或者拔出PC机.当连接PC和交换机时,请先安装配置电缆的DB-9端到PC机,再连接R ...
- JWT: 基于Token的验证
现在SPA(Single Page Application, 单页面应用)和前后端分离已经是主流. 基于Token的验证非常适合这种构架. Difference between Token-based ...
最新文章
- Java 语言中十大“坑爹”功能!
- jQuery成为微软.NET开发工具的一部分了
- 使用css3制作正方形、三角形、扇形和饼状图
- 广告行业一些常用物料的尺寸
- iOS音频播放 (二):AudioSession 转
- 金融数据分析与挖掘实战1.7-1.8
- mysql 事务操作详细实例介绍
- bootstrap 点击图片放大查看_Bootstrap 开源 SVG 图标库 Bootstrap Icons
- 02 ZooKeeper分布式集群安装
- 【uniapp公众号分销商城从0到1】手机验证注册登录(04)
- itext7+poi实现excel转pdf
- Pytorch的一些小问题
- open sl java audio_webrtc学习(二): audio_device之opensles
- 由浅入深了解羚珑平台统一接入服务 —— Monet
- 一种有手就行的物联网平台的多终端接入方法
- 对当前医院奖金分配制度的探讨
- 20162316刘诚昊 第七周学习报告
- MacBook杀毒软件CleanMyMac X4.13
- 使用hardhat将合约部署到ganache
- 利用计算机阅读的文献是什么,文献检索作业
热门文章
- 让curl命令支持http2
- 图片缩放并设置dpi
- 产品学习(二)——竞品分析
- Android项目jenkins自动化构建之360加固(一)
- 自动填地址(拼多多)
- vscode多行注释
- html5 mac os 开发工具,如何通过TextMate或命令行在Mac OS上使用W3C tidy-html5软件包?...
- Apache NIFI 安装 ● 操作 ● 文件同步 ● oracle 数据库增量同步实例讲解
- AndroidRNFlutter实战——防抖节流函数
- MAXAR、Planet Labs、BlackSky Global——美国遥感卫星数据行业的巨头