上海做网站 公司 哪家好,博客系统做网站,建设局是干嘛的单位,电子商务网站设计与维护论文前言
代码可以参考 需要把Web应用做成无状态的#xff0c;即服务器端无状态#xff0c;就是说服务器端不会存储像会话这种东西#xff0c;而是每次请求时access_token进行资源访问。这里我们将使用 JWT 1#xff0c;基于散列的消息认证码#xff0c;使用一个密钥和一个消…前言
代码可以参考 需要把Web应用做成无状态的即服务器端无状态就是说服务器端不会存储像会话这种东西而是每次请求时access_token进行资源访问。这里我们将使用 JWT 1基于散列的消息认证码使用一个密钥和一个消息作为输入生成它们的消息摘要。该密钥只有服务端知道。访问时使用该消息摘要进行传播服务端然后对该消息摘要进行验证。
认证步骤
客户端第一次使用用户名密码访问认证服务器服务器验证用户名和密码认证成功,使用用户密钥生成JWT并返回之后每次请求客户端带上JWT服务器对JWT进行验证
自定义 jwt 拦截器
/*** oauth2拦截器现在改为 JWT 认证*/
public class OAuth2Filter extends FormAuthenticationFilter {/*** 设置 request 的键用来保存 认证的 userID,*/private final static String USER_ID USER_ID;Resourceprivate JwtUtils jwtUtils;/*** logger*/private static final Logger LOGGER LoggerFactory.getLogger(OAuth2Filter.class);/*** shiro权限拦截核心方法 返回true允许访问resource** param request* param response* param mappedValue* return*/Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {String token getRequestToken((HttpServletRequest) request);try {// 检查 token 有效性//ExpiredJwtException JWT已过期//SignatureException JWT可能被篡改Jwts.parser().setSigningKey(jwtUtils.getSecret()).parseClaimsJws(token).getBody();} catch (Exception e) {// 身份验证失败返回 false 将进入onAccessDenied 判断是否登陆。onLoginFail(response);return false;}Long userId getUserIdFromToken(token);// 存入到 request 中在后面的业务处理中可以使用request.setAttribute(USER_ID, userId);return true;}/*** 当访问拒绝时是否已经处理了* 如果返回true表示需要继续处理* 如果返回false表示该拦截器实例已经处理完成了将直接返回即可。** param request* param response* return* throws Exception*/Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {if (isLoginRequest(request, response)) {if (isLoginSubmission(request, response)) {return executeLogin(request, response);} else {return true;}} else {onLoginFail(response);return false;}}/*** 鉴定失败返回错误信息* param token* param e* param request* param response* return*/Overrideprotected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {try {((HttpServletResponse) response).setStatus(HttpStatus.BAD_REQUEST.value());response.getWriter().print(账号活密码错误);} catch (IOException e1) {LOGGER.error(e1.getMessage(), e1);}return false;}/*** token 认证失败** param response*/private void onLoginFail(ServletResponse response) {HttpServletResponse httpResponse (HttpServletResponse) response;((HttpServletResponse) response).setStatus(HttpStatus.UNAUTHORIZED.value());try {response.getWriter().print(没有权限请联系管理员授权);} catch (IOException e) {LOGGER.error(e.getMessage(), e);}}/*** 获取请求的token*/private String getRequestToken(HttpServletRequest httpRequest) {//从header中获取tokenString token httpRequest.getHeader(jwtUtils.getHeader());//如果header中不存在token则从参数中获取tokenif (StringUtils.isBlank(token)) {return httpRequest.getParameter(jwtUtils.getHeader());}if (StringUtils.isBlank(token)) {// 从 cookie 获取 tokenCookie[] cookies httpRequest.getCookies();if (null cookies || cookies.length 0) {return null;}for (Cookie cookie : cookies) {if (cookie.getName().equals(jwtUtils.getHeader())) {token cookie.getValue();break;}}}return token;}/*** 根据 token 获取 userID** param token token* return userId*/private Long getUserIdFromToken(String token) {if (StringUtils.isBlank(token)) {throw new KCException(无效 token, HttpStatus.UNAUTHORIZED.value());}Claims claims jwtUtils.getClaimByToken(token);if (claims null || jwtUtils.isTokenExpired(claims.getExpiration())) {throw new KCException(jwtUtils.getHeader() 失效请重新登录, HttpStatus.UNAUTHORIZED.value());}return Long.parseLong(claims.getSubject());}}将自定义shiro拦截器设置到 ShiroFilterFactoryBean 中然后将需要进行权限验证的 path 进行设置拦截过滤。
登陆 PostMapping(/login)ApiOperation(系统登陆)public ResponseEntityString login(RequestBody SysUserLoginForm userForm) {String kaptcha ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);if (!userForm.getCaptcha().equalsIgnoreCase(kaptcha)) {throw new KCException(验证码不正确);}UsernamePasswordToken token new UsernamePasswordToken(userForm.getUsername(), userForm.getPassword());Subject currentUser SecurityUtils.getSubject();currentUser.login(token);//账号锁定if (getUser().getStatus() SysConstant.SysUserStatus.LOCK) {throw new KCException(账号已被锁定,请联系管理员);}// 登陆成功后直接返回 token ,然后后续放到 header 中认证return ResponseEntity.status(HttpStatus.OK).body(jwtUtils.generateToken(getUserId()));}JwtUtils
我前面给 jwt 设置了三个参数
# jwt 配置
jwt:# 加密密钥secret: 61D73234C4F93E03074D74D74D1E39D9 #blog.wuwii.com# token有效时长expire: 7 # 7天单位天# token 存在 header 中的参数header: tokenjwt 工具类的编写
ConfigurationProperties(prefix jwt)
Component
public class JwtUtils {/*** logger*/private Logger logger LoggerFactory.getLogger(JwtUtils.class);/*** 密钥*/private String secret;/*** 有效期限*/private int expire;/*** 存储 token*/private String header;/*** 生成jwt token** param userId 用户ID* return token*/public String generateToken(long userId) {Date nowDate new Date();return Jwts.builder().setHeaderParam(typ, JWT)// 后续获取 subject 是 userid.setSubject(userId ).setIssuedAt(nowDate).setExpiration(DateUtils.addDays(nowDate, expire))// 这里我采用的是 HS512 算法.signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 解析 token* 利用 jjwt 提供的parser传入秘钥** param token token* return 数据声明 MapString, Object*/public Claims getClaimByToken(String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {return null;}}/*** token是否过期** return true过期*/public boolean isTokenExpired(Date expiration) {return expiration.before(new Date());}public String getSecret() {return secret;}public void setSecret(String secret) {this.secret secret;}public int getExpire() {return expire;}public void setExpire(int expire) {this.expire expire;}public String getHeader() {return header;}public void setHeader(String header) {this.header header;}
}总结
由于 JWT 这种方式服务端不需要保存任何状态所以服务端不需要使用 session 保存用户信息单元测试也比较方便虽然中间转码解码会消耗一些性能但是影响不大还比较方便的应用在 SSO (Single Sign On )。