分布式构架中用户登陆的验证方式

之前在i西科的项目里看到了这样一段代码

注释里说的很清楚,就是用来验证用户登陆的。他这里用了登陆凭证的方式来验证登陆,而我之前写的项目里都是直接在服务器上保存session,于是我去查了一下这两种方式的区别,这里大致介绍一下。 

先来看使用session验证的代码:

请求体:

/**
 * 用户登陆请求体
 * 
 * @author xuLiang
 * @since 0.0.1
 */
public class UserLogin {
	private Long id;
	private String password;

getters and setters...
}

DAO层:

/**
	 * 
	 * 查找登陆用户账号密码是否匹配
	 * 
	 * @param userLogin
	 *            用户登陆请求体
	 * @return true or false
	 * @author xuLiang
	 * @since 0.0.1
	 */
	@Override
	public boolean findByIdAndPassword(UserLogin userLogin) {
		UsersRecord record = dsl.selectFrom(USERS)
				.where(USERS.ID.eq(userLogin.getId()).and(USERS.PASSWORD.eq(userLogin.getPassword()))).fetchOne();
		if (record != null) {
			return true;
		}
		return false;
	}

service层:

/**
	 * 
	 * 验证用户登陆
	 * 
	 * @param userLogin
	 *            用户登陆请求体
	 * @return true or false
	 * @author xuLiang
	 * @since 0.0.1
	 */
	@Override
	public boolean verifyLogin(UserLogin userLogin) {
		return usersMapper.findByIdAndPassword(userLogin);
	}

controller:

	/**
	 * 用户登陆验证
	 * 
	 * @param userLogin
	 *            用户登录请求体
	 * @return SESSION
	 * @author xuLiang
	 * @since 0.0.1
	 */
	@PostMapping("/loginVerify")
	@ResponseStatus(HttpStatus.OK)
	public String loginVerify(@RequestBody UserLogin userLogin, HttpSession session) {
		boolean verify = userPersonService.verifyLogin(userLogin);
		if (verify) {
			session.setAttribute(WebSecurityConfig.SESSION_KEY, userLogin.getId());
			return "index";
		}
		return "redirect:/login";
	}

       /**
	 * 用户登出移除session
	 * 
	 * @param userLogin
	 *            用户登录请求体
	 * @return SESSION
	 * @author xuLiang
	 * @since 0.0.1
	 */
	@GetMapping("/logout")
	public String logout(HttpSession session) {
		session.removeAttribute(WebSecurityConfig.SESSION_KEY);
		return "redirect:/login";
	}

拦截器:

/**
 * 
 * web拦截器类
 * </p>
 *
 * @author xuLiang
 * @since 0.0.1
 */
@Configuration
public class WebSecurityConfig extends WebMvcConfigurerAdapter {

	/**
	 * 登录session key
	 */
	public final static String SESSION_KEY = "id";

	@Bean
	public SecurityInterceptor getSecurityInterceptor() {
		return new SecurityInterceptor();
	}

	public void addInterceptors(InterceptorRegistry registry) {
		InterceptorRegistration addInterceptor = registry.addInterceptor(getSecurityInterceptor());

		// 排除配置
		addInterceptor.excludePathPatterns("/error");
		addInterceptor.excludePathPatterns("/login**");

		// 拦截配置
		addInterceptor.addPathPatterns("/");
		addInterceptor.addPathPatterns("/index");
	}

	private class SecurityInterceptor extends HandlerInterceptorAdapter {

		@Override
		public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
				throws Exception {
			HttpSession session = request.getSession();
			if (session.getAttribute(SESSION_KEY) != null)
				return true;

			// 跳转登录
			String url = "/login";
			response.sendRedirect(url);
			return false;
		}

	}

}

session的工作方式很简单,就是用户登陆成功后服务器上便会保存用户的唯一session ,当用户登出或者关闭浏览器后,该session便会被清除。但是这种方式的弊端也很明显:假设有这样一个场景,系统的数据量达到千万级,需要几台服务器部署,当一个用户在其中一台服务器登录后,用session保存其登录信息,其他服务器怎么知道该用户登录了?当然解决办法有,可以用spring-session。如果该系统同时为移动端服务呢?移动端通过url向后台要数据,如果用session,通过sessionId识别用户,万一sessionId被截获了,别人可以利用sessionId向后台要数据,就有安全隐患了。服务端不需要存储任何用户的信息,用户的验证应该放在客户端,jwt就是这种方式。

这里重点说一下springboot整合jwt。

1、什么是JWT?
JWT(Json Web Token),是一种工具,格式为XXXX.XXXX.XXXX的字符串,JWT以一种安全的方式在用户和服务器之间传递存放在JWT中的不敏感信息。

2、为什么要用JWT?
设想这样一个场景,在我们登录一个网站之后,再把网页或者浏览器关闭,下一次打开网页的时候可能显示的还是登录的状态,不需要再次进行登录操作,通过JWT就可以实现这样一个用户认证的功能。当然使用Session可以实现这个功能,但是使用Session的同时也会增加服务器的存储压力,而JWT是将存储的压力分布到各个客户端机器上,从而减轻服务器的压力。

3、JWT长什么样?
JWT由3个子字符串组成,分别为Header,Payload以及Signature,结合JWT的格式即:Header.Payload.Signature。(Claim是描述Json的信息的一个Json,将Claim转码之后生成Payload)。

Header是由以下这个格式的Json通过Base64编码(编码不是加密,是可以通过反编码的方式获取到这个原来的Json,所以JWT中存放的一般是不敏感的信息)生成的字符串,Header中存放的内容是说明编码对象是一个JWT以及使用“SHA-256”的算法进行加密(加密用于生成Signature)

{
"typ":"JWT",
"alg":"HS256"
} 

Claim是一个Json,Claim中存放的内容是JWT自身的标准属性,所有的标准属性都是可选的,可以自行添加,比如:JWT的签发者、JWT的接收者、JWT的持续时间等;同时Claim中也可以存放一些自定义的属性,这个自定义的属性就是在用户认证中用于标明用户身份的一个属性,比如用户存放在数据库中的id,为了安全起见,一般不会将用户名及密码这类敏感的信息存放在Claim中。将Claim通过Base64转码之后生成的一串字符串称作Payload。

{ 
    "iss":"Issuer —— 用于说明该JWT是由谁签发的", 
    "sub":"Subject —— 用于说明该JWT面向的对象", 
    "aud":"Audience —— 用于说明该JWT发送给的用户", 
    "exp":"Expiration Time —— 数字类型,说明该JWT过期的时间", 
    "nbf":"Not Before —— 数字类型,说明在该时间之前JWT不能被接受与处理", 
    "iat":"Issued At —— 数字类型,说明该JWT何时被签发", 
    "jti":"JWT ID —— 说明标明JWT的唯一ID", 
    "user-definde1":"自定义属性举例", 
    "user-definde2":"自定义属性举例" 
} 

Signature是由Header和Payload组合而成,将Header和Claim这两个Json分别使用Base64方式进行编码,生成字符串Header和Payload,然后将Header和Payload以Header.Payload的格式组合在一起形成一个字符串,然后使用上面定义好的加密算法和一个密匙(这个密匙存放在服务器上,用于进行验证)对这个字符串进行加密,形成一个新的字符串,这个字符串就是Signature。

4、JWT实现认证的原理
服务器在生成一个JWT之后会将这个JWT会以Authorization : Bearer JWT 键值对的形式存放在cookies里面发送到客户端机器,在客户端再次访问收到JWT保护的资源URL链接的时候,服务器会获取到cookies中存放的JWT信息,首先将Header进行反编码获取到加密的算法,在通过存放在服务器上的密匙对Header.Payload 这个字符串进行加密,比对JWT中的Signature和实际加密出来的结果是否一致,如果一致那么说明该JWT是合法有效的,认证成功,否则认证失败。

springboot使用:

引入依赖:

		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.7.0</version>
		</dependency>

jwt配置类:

/**
 * 配置jwt,对指定链接下的所有资源访问进行JWT的验证,类似session的拦截器
 *
 * @author xuLiang
 * since 0.0.1
 *
 **/
@Configuration
public class JwtCfg {

    @Bean
    public FilterRegistrationBean jwtFilter() {
        final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new JwtFilter());
        registrationBean.addUrlPatterns("/index");//指定需要验证的URL
        return registrationBean;
    }

}

JwtFilter 类:
这个类声明了一个JWT过滤器类,从Http请求中提取JWT的信息,并使用了”jwt_key”这个密匙对JWT进行验证,代码来自官方文档,此类没有特殊需求可以直接使用默认的配置,此处Token类型是Bearer Token,也有OAuth Token,Basic Token等等。。在filter中验证token时,过期和非法的token都会抛出异常,可以自定义bean继承自BasicErrorController来进行统一的异常处理(返回给前端固定的Json内容,实际使用时和Js交互还会遇到跨域问题,要给response加上相关的请求头)。

public class JwtFilter extends GenericFilterBean{
    //Jwtconfig中配置的filter 用于Jwt token的验证工作 配置时可以指定对应的路径
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        String authHeader = request.getHeader("Authorization");
 
        //规避探测性质的 OPTIONS请求
        String optionsString = "OPTIONS";
        String bearerString = "Bearer ";
        if (optionsString.equals(request.getMethod())){
            response.setStatus(HttpServletResponse.SC_OK);
            filterChain.doFilter(servletRequest, servletResponse);
        }else {
            //验证token
            if (StringUtils.isEmpty(authHeader) || !authHeader.startsWith(bearerString)){
                    throw new ServletException(new TokenException(-1, "Missing or invalid Authorization header"));
            }else {
                String token = authHeader.substring(bearerString.length());
                try {
                    //使用jwt paser来验证签名
                    Claims claims = Jwts.parser().setSigningKey("jwt_key").parseClaimsJws(token).getBody();
                    request.setAttribute("claims", claims);
                }catch (ExpiredJwtException e){
                    throw new ServletException(new TokenException(-2, "token expired"));
                }catch (SignatureException e){
                    throw new ServletException(new TokenException(-3, "token invalid"));
                }catch (Exception e){
                    throw new ServletException(new TokenException(-4, "error"));
                }
 
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
 
    }
 
}

controller代码稍作修改:

@PostMapping("/loginVerify")
@ResponseStatus(HttpStatus.OK)
public String loginVerify(@RequestBody UserLogin userLogin) {
	boolean verify = userPersonService.verifyLogin(userLogin);
		if (verify) {
			String jwtToken =Jwts.builder().setSubject(userLogin.getid()).claim(userLogin.getId())
                        .setIssuedAt(new Date()).signWith(SignatureAlgorithm.HS256, "jwt_key").compact();

                return jwtToken;//给用户返回一个凭证Token

		}
		return "redirect:/login";
	}

至此jwt就算配置好了,在用户成功登陆后就可以看到返回的Token了。

顺便说说之前在使用python爬虫的时候遇到过一些网站一直对请求报500,明明用户名和密码都是对的还出现这种情况,在接触了jwt之后,立马把爬虫的请求头里加上

'Authorization':'Bearer '+token,

发现部分网站可以成功访问了,当然请求头还要取决于Token类型,如果是报403估计就是网站设置了反爬虫。。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值