用jwt主要解决前后端分离导致的2个问题
前提:前后端不分离时候解决跨域问题可以用token解决或者corf解决,前后端分离后由于访问后台本身就是跨域(ajax)所以解决方案失效
解决:jwt本身就是一个有某种规范的token,可以解决csrf问题
前提:前后端不分离的时候,每次访问请求游览器会自动携带cookie,cookie包含了jsessionId,用来维持session状态。当前后端分离的时候,ajax肯定是不会主动携带cookie信息的,所以我们可以通过设置前后端的头部信息达到一样的目的。但是虽然解决了session问题,csrf问题又没法解决了。
解决:jwt本身就是一个有某种规范的token,里面可以包括用户id,利用用户id可以维持某些状态,替代session
进行方案比较的时候一定要根据业务场景来使用,我觉得采用jwt的时候肯定是一个较为小型的应用,安全性能不会太高,能满足一定的认证与授权的要求,采用jwt的方案需要解决的一个问题是jwt的失效和续期问题。而如果采用oauth2的时候则是一个较为大型的认证授权框架,适合分布式、微服务、对外api等等,应用较大。
返回的jwt-token可以通过存储cookie或者header等等返回给客户端
通过前台存储的jwt-token,一般存储在cookie跟着请求一起到达后台
首先,服务器应用(下面简称“应用”)让用户通过Web表单将自己的用户名和密码发送到服务器的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
通过继承AbstractAuthenticationProcessingFilter 创建一个认证过滤器,该过滤器只拦截login,通过回去传入的用户名和密码进行认证,并且重写了认证成功后的具体操作。
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
public JWTLoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException {
SysUser creds = new ObjectMapper().readValue(req.getInputStream(), SysUser.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
Collections.emptyList()
)
);
}
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
TokenAuthenticationService.addAuthentication(res, auth.getName());
}
}
首先重写UserDetail对象,此对象的作用是方便UserDetailsService返回一个
public class SysUser implements UserDetails
接着重写一个UserDetailsService对象,返回上方重写的user对象。(此对象需要被传入DaoProviderManager进行用户认证)
@Service
public class UserService implements UserDetailsService {
@Autowired
SysUserRepository sysUserRepository;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
SysUser user = sysUserRepository.findByUsername(s);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
return user;
}
用户名和密码成功后,应用将用户的id
(图中的user_id
)作为JWT Payload的一个属性,将其与头部分别进行Base64编码拼接后签名,形成一个JWT。这里的JWT就是一个形同lll.zzz.xxx
的字符串。
认证成功后将token放入cookie或者head返回,这里推荐放到cookie即可
class TokenAuthenticationService {
static final long EXPIRATIONTIME = 1000*60*60*24*1; // 1 days
static final String SECRET = "ThisIsASecret";
static final String TOKEN_PREFIX = "Bearer";
static final String HEADER_STRING = "Authorization";
/**
* 创建jwt
* @param res
* @param username
*/
static void addAuthentication(HttpServletResponse res, String username) {
String JWT = Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT);
// Cookie cookie=new Cookie("llg","liliguang");
// res.addCookie(cookie);
}
应用将JWT字符串作为该请求Cookie的一部分返回给用户。注意,在这里必须使用HttpOnly
属性来防止Cookie被JavaScript读取,从而避免跨站脚本攻击(XSS攻击)。
在Cookie失效或者被删除前,用户每次访问应用,应用都会接受到含有jwt
的Cookie。从而应用就可以将JWT从请求中提取出来。
应用通过一系列任务检查JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
应用在确认JWT有效之后,JWT进行Base64解码(可能在上一步中已经完成),然后在Payload中读取用户的id值,也就是user_id
属性。这里用户的id
为1025。
应用从数据库取到id
为1025的用户的信息,加载到内存中,进行ORM之类的一系列底层逻辑初始化。
应用根据用户请求进行响应。
通过继承GenericFilterBean 创建一个过滤器,专门负责提取jwt-token并且解析之后将其构造成一个authenication放到SecurityContextHolder中。(SecurityContextHolder是一个多线程的用户安全上下文环境,负责维护用户状态)
由于spring-security的过滤器链没有从头部信息拿token的过滤器,所以我们需要创建一个并且添加到过滤器链中。之后通过request对象拿到jwt-token进行解析校验
public class JWTAuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest)request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request,response);
}
}
static Authentication getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
return user != null ?
new UsernamePasswordAuthenticationToken(user, null, emptyList()) :
null;
}
return null;
}
参考作者:John Wu
原文链接:http://blog.leapoahead.com/2015/09/07/user-authentication-with-jwt/
版权归作者所有,转载请注明出处
参考作者:xjtushilei
原文链接:https://hacpai.com/article/1496982153867
版权归作者所有,转载请注明出处
代码 github 里,https://github.com/xjtushilei/spring-boot-security