之前的后台由smm+shiro搭建,最近想要pc端实现前后端分离,但移动端和管理端这些公用一个,想要实现前后端分离,而且不影响管理端的操作,管理端是由session存储用户信息,之前的pc端也是,通过shiro实现登录授权认证,现在pc端实现前后分离,这样的话前后交互不安全,同时存在跨域的问题,因此,想实现pc端前后分离,不影响 管理端,所以pc端采用jwt生成token,进行每次操作的验证。
JWT(JSON Web Tokens)是一种用于安全的传递信息而采用的一种标准。Web系统中,我们使用加密的Json来生成Token在服务端与客户端无状态传输,代替了之前常用的Session。
系统采用Redis作为缓存,解决Token过期更新的问题。
TokenExpiredException
异常时说明Token过期,校验时间戳一致后重新生成Token并调用登录方法。在使用jwt时 maven中添加以下
io.jsonwebtoken jjwt 0.6.0
jwt生成
/** * 生成token * @param id * @param subject * @param ttlMillis * @param claims * @return * @throws Exception */ public static String createJWT(String id, String subject, long ttlMillis,Mapclaims) throws Exception { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。 long nowMillis = System.currentTimeMillis();// 生成JWT的时间 Date now = new Date(nowMillis); SecretKey key = generalKey(); // 下面就是在为payload添加各种标准声明和私有声明了 JwtBuilder builder = Jwts.builder() // 这里其实就是new一个JwtBuilder,设置jwt的body .setClaims(claims) // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setId(id) // 设置jti(JWT // ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 .setIssuedAt(now) // iat: jwt的签发时间 .setSubject(subject) // sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。 .signWith(signatureAlgorithm, key);// 设置签名使用的签名算法和签名使用的秘钥 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); // 设置过期时间 } return builder.compact(); // 就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt }
/** * 解密jwt * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { try { SecretKey key = generalKey(); // 签名秘钥,和生成的签名的秘钥一模一样 Claims claims = Jwts.parser() // 得到DefaultJwtParser .setSigningKey(key) // 设置签名的秘钥 .parseClaimsJws(jwt).getBody();// 设置需要解析的jwt return claims; }catch (ExpiredJwtException e){ return e.getClaims(); } }
在applicationContext.xml中添加
/public**/**= statelessAuth
以上中myStatelessFilter是自己定义的,如下:
public class MyStatelessShiroFilter extends AccessControlFilter { private Logger logger = LoggerFactory.getLogger(MyStatelessShiroFilter.class); /** *返回false * @param servletRequest * @param servletResponse * @param o * @return 返回结果是false的时候才会执行下面的onAccessDenied方法 * @throws Exception */ @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { logger.info("is access allowed"); return false; } /** * 从请求头获取token并验证,验证通过后交给realm进行登录 * @param servletRequest * @param servletResponse * @return 返回结果为true时,表明登录认证通过,执行controller层 * @throws Exception */ @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { logger.info("on access denied"); HttpServletRequest request = (HttpServletRequest) servletRequest; String jwt = request.getHeader("Authorization"); if (JwtUtil.verifyToken(jwt)) { UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(jwt, jwt); try { //委托realm进行登录认证 getSubject(servletRequest, servletResponse).login(usernamePasswordToken); return true; }catch (Exception e) { return false; } } redirectToLogin(servletRequest,servletResponse); return false; } /** * 重定向到登录页 * @param request * @param response * @throws IOException */ @Override protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException { logger.info("redirectToLogin"); WebUtils.issueRedirect(request, response, "/login"); }
controller中,jwt调用和token的存储
String jwt = JwtUtil.createToken(map); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(jwt, jwt); subject.login(usernamePasswordToken); Jedis jedis =SignatureUtil.pcConnectRedis();//连接redis String token= RSAUtils.createRSAContent(account.getSys_account_id(), RSAUtils.PUB_KEY);//加密 jedis.set(token, jwt); tokeMap.put("token",token);
此处是登录成功后将生成的token存储到redis,以便以后每次访问是从redis中获取进行验证,如果token失效则让用户重新登录。