乐优商城第十八天(授权中心与登陆)

很早之前,听人家做淘淘商城的人一直说单点登录,但是一直不明白单点登陆是什么,看百度百科


如果是这样的话,那么我们这个应该也算是一种单点登陆的解决方案。

我们的登陆是服务端无状态的登陆

采用jwt+rsa对称式加密算法来生成一个令牌,保存在浏览器,浏览器下次可以通过携带cookie来,我们用公钥对其解密,如果能够解出其中的信息,没那么证明这个令牌是正确的,这个人已经登陆。

jwt生成的token是3部分组成的,

头部,协议信息以及加密方式

载荷,用户的信息

签名,前两部分,再加上密钥,加密生成的,用于验证整个数据的完整性和可靠性。


我们这里用到了几个工具类

RsA加密,生成公钥,私钥的方法

/**
 * Created by ace on 2018/5/10.
 *
 * @author HuYi.Zhang
 */
public class RsaUtils {
    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(byte[] bytes) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}

载荷中的信息,保存在一个类中

public abstract class JwtConstans {
    public static final String JWT_KEY_ID = "id";
    public static final String JWT_KEY_USER_NAME = "username";
}

jwt生成token的方法

/**
 * @author: HuYi.Zhang
 * @create: 2018-05-26 15:43
 **/
public class JwtUtils {
    /**
     * 私钥加密token
     *
     * @param userInfo      载荷中的数据
     * @param privateKey    私钥
     * @param expireMinutes 过期时间,单位秒
     * @return
     * @throws Exception
     */
    public static String generateToken(UserInfo userInfo, PrivateKey privateKey, int expireMinutes) throws Exception {
        return Jwts.builder()
                .claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
                .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(SignatureAlgorithm.RS256, privateKey)
                .compact();
    }

    /**
     * 私钥加密token
     *
     * @param userInfo      载荷中的数据
     * @param privateKey    私钥字节数组
     * @param expireMinutes 过期时间,单位秒
     * @return
     * @throws Exception
     */
    public static String generateToken(UserInfo userInfo, byte[] privateKey, int expireMinutes) throws Exception {
        return Jwts.builder()
                .claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
                .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(SignatureAlgorithm.RS256, RsaUtils.getPrivateKey(privateKey))
                .compact();
    }

    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥
     * @return
     * @throws Exception
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥字节数组
     * @return
     * @throws Exception
     */
    private static Jws<Claims> parserToken(String token, byte[] publicKey) throws Exception {
        return Jwts.parser().setSigningKey(RsaUtils.getPublicKey(publicKey))
                .parseClaimsJws(token);
    }

    /**
     * 获取token中的用户信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     * @throws Exception
     */
    public static UserInfo getInfoFromToken(String token, PublicKey publicKey) throws Exception {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        return new UserInfo(
                ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
                ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
        );
    }

    /**
     * 获取token中的用户信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     * @throws Exception
     */
    public static UserInfo getInfoFromToken(String token, byte[] publicKey) throws Exception {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        return new UserInfo(
                ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
                ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
        );
    }
}

将对象转化为各种数据类型的工具类

public class ObjectUtils {

    public static String toString(Object obj) {
        if (obj == null) {
            return null;
        }
        return obj.toString();
    }

    public static Long toLong(Object obj) {
        if (obj == null) {
            return 0L;
        }
        if (obj instanceof Double || obj instanceof Float) {
            return Long.valueOf(StringUtils.substringBefore(obj.toString(), "."));
        }
        if (obj instanceof Number) {
            return Long.valueOf(obj.toString());
        }
        if (obj instanceof String) {
            return Long.valueOf(obj.toString());
        } else {
            return 0L;
        }
    }

    public static Integer toInt(Object obj) {
        return toLong(obj).intValue();
    }
}

我们再创建一个实体类,来接受解析后的用户信息

public class UserInfo {

    private Long id;

    private String username;

当用户第一次请求的时候,我们会验证用户名和密码,并生成token

/**
 * 登陆获取令牌的方法
 * @param username
 * @param password
 * @param request
 * @param response
 * @return
 */
@PostMapping("accredit")
public ResponseEntity<Void> authorization(
        @RequestParam("username") String username,
        @RequestParam("password") String password,
        HttpServletRequest request,
        HttpServletResponse response
        ){
    String token = this.authService.getToken(username,password);
    if (StringUtils.isBlank(token)){
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
    //将令牌放到cookie,httponly设置为true,防止js修改
    CookieUtils.setCookie(request,response,jwtProperties.getCookieName(),token,jwtProperties.getCookieMaxAge(),null,true);
    return ResponseEntity.status(HttpStatus.OK).build();
}

service

/**
 *获取令牌的方法
 * @param username
 * @param password
 * @return
 */
public String getToken(String username, String password) {
    try {
        ResponseEntity<User> userResponseEntity = this.userClient.queryUser(username, password);
        if (!userResponseEntity.hasBody()) {
            logger.info("用户信息不存在,{}", username);
            return null;
        }
        User user = userResponseEntity.getBody();
        //生成令牌
        UserInfo userInfo = new UserInfo();
        BeanUtils.copyProperties(user, userInfo);
        String token = JwtUtils.generateToken(userInfo, jwtProperties.getPrivateKey(), jwtProperties.getExpire());
        return token;
    } catch (Exception e) {
        logger.error("生成令牌的过程中出错");
        return null;
    }
}

这个时候还需要一个工具类,cookieUtils

/**
 * 
 * Cookie 工具类
 *
 */
public final class CookieUtils {

   static final Logger logger = LoggerFactory.getLogger(CookieUtils.class);

   /**
    * 得到Cookie的值, 不编码
    * 
    * @param request
    * @param cookieName
    * @return
    */
   public static String getCookieValue(HttpServletRequest request, String cookieName) {
      return getCookieValue(request, cookieName, false);
   }

   /**
    * 得到Cookie的值,
    * 
    * @param request
    * @param cookieName
    * @return
    */
   public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
      Cookie[] cookieList = request.getCookies();
      if (cookieList == null || cookieName == null){
         return null;         
      }
      String retValue = null;
      try {
         for (int i = 0; i < cookieList.length; i++) {
            if (cookieList[i].getName().equals(cookieName)) {
               if (isDecoder) {
                  retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
               } else {
                  retValue = cookieList[i].getValue();
               }
               break;
            }
         }
      } catch (UnsupportedEncodingException e) {
         logger.error("Cookie Decode Error.", e);
      }
      return retValue;
   }

   /**
    * 得到Cookie的值,
    * 
    * @param request
    * @param cookieName
    * @return
    */
   public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
      Cookie[] cookieList = request.getCookies();
      if (cookieList == null || cookieName == null){
         return null;         
      }
      String retValue = null;
      try {
         for (int i = 0; i < cookieList.length; i++) {
            if (cookieList[i].getName().equals(cookieName)) {
               retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
               break;
            }
         }
      } catch (UnsupportedEncodingException e) {
         logger.error("Cookie Decode Error.", e);
      }
      return retValue;
   }

   /**
    * 生成cookie,并指定编码
    * @param request 请求
    * @param response 响应
    * @param cookieName name
    * @param cookieValue value
    * @param encodeString 编码
    */
   public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, String encodeString) {
      setCookie(request,response,cookieName,cookieValue,null,encodeString, null);
   }

   /**
    * 生成cookie,并指定生存时间
    * @param request 请求
    * @param response 响应
    * @param cookieName name
    * @param cookieValue value
    * @param cookieMaxAge 生存时间
    */
   public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, Integer cookieMaxAge) {
      setCookie(request,response,cookieName,cookieValue,cookieMaxAge,null, null);
   }

   /**
    * 设置cookie,不指定httpOnly属性
    */
   public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, Integer cookieMaxAge, String encodeString) {
      setCookie(request,response,cookieName,cookieValue,cookieMaxAge,encodeString, null);
   }

   /**
    * 设置Cookie的值,并使其在指定时间内生效
    * 
    * @param cookieMaxAge
    *            cookie生效的最大秒数
    */
   public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, Integer cookieMaxAge, String encodeString, Boolean httpOnly) {
      try {
         if(StringUtils.isBlank(encodeString)) {
            encodeString = "utf-8";
         }

         if (cookieValue == null) {
            cookieValue = "";
         } else {
            cookieValue = URLEncoder.encode(cookieValue, encodeString);
         }
         Cookie cookie = new Cookie(cookieName, cookieValue);
         if (cookieMaxAge != null && cookieMaxAge > 0)
            cookie.setMaxAge(cookieMaxAge);
         if (null != request)// 设置域名的cookie
            cookie.setDomain(getDomainName(request));
         cookie.setPath("/");

         if(httpOnly != null) {
            cookie.setHttpOnly(httpOnly);
         }
         response.addCookie(cookie);
      } catch (Exception e) {
         logger.error("Cookie Encode Error.", e);
      }
   }

   /**
    * 得到cookie的域名
    */
   private static final String getDomainName(HttpServletRequest request) {
      String domainName = null;

      String serverName = request.getRequestURL().toString();
      if (serverName == null || serverName.equals("")) {
         domainName = "";
      } else {
         serverName = serverName.toLowerCase();
         serverName = serverName.substring(7);
         final int end = serverName.indexOf("/");
         serverName = serverName.substring(0, end);
         final String[] domains = serverName.split("\\.");
         int len = domains.length;
         if (len > 3) {
            // www.xxx.com.cn
            domainName = domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
         } else if (len <= 3 && len > 1) {
            // xxx.com or xxx.cn
            domainName = domains[len - 2] + "." + domains[len - 1];
         } else {
            domainName = serverName;
         }
      }

      if (domainName != null && domainName.indexOf(":") > 0) {
         String[] ary = domainName.split("\\:");
         domainName = ary[0];
      }
      return domainName;
   }

}

这个时候,我们的令牌已经下发了,但是我们只给了令牌30分钟的生存时间,每当用户有操作,也就是每当用户查询用户个人信息的话,就给他重新生成一个token。

/**
 * 用户操作,需要刷新token
 * @param token
 * @param request
 * @param response
 * @return
 */
@GetMapping("verify")
public ResponseEntity<UserInfo> getUserInfo(
        @CookieValue("LY_TOKEN") String token,
        HttpServletRequest request,
        HttpServletResponse response
){
    try {
        UserInfo userInfo = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
        //只要用户有这个请求,就给用户一个新的token,防止过期
        String newToken = JwtUtils.generateToken(userInfo, jwtProperties.getPrivateKey(), jwtProperties.getExpire());
        //这里的true谁不允许js操作的意思
        CookieUtils.setCookie(request,response,jwtProperties.getCookieName(),
                newToken,jwtProperties.getCookieMaxAge(),null,true);
        return ResponseEntity.status(HttpStatus.OK).body(userInfo);
    } catch (Exception e) {
        e.printStackTrace();
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
}

为了保证某些服务是登陆的用户才能访问的,我们需要在网关设置拦截器

拦截器如下,因为是登陆的校验,肯定是一个前置的拦截器

@Component
@EnableConfigurationProperties(value = {JwtProperties.class, FilterProperties.class})
public class LoginFilter extends ZuulFilter {

    private Logger logger = LoggerFactory.getLogger(LoginFilter.class);

    @Autowired
    private FilterProperties filterProperties;

    @Autowired
    private JwtProperties jwtProperties;

    private Boolean isAllowPath(String uri){
        List<String> allowPaths = filterProperties.getAllowPaths();
        boolean isFind = false;
        for (String allowPath : allowPaths) {
            if (uri.startsWith(allowPath)){
             isFind = true;
             break;
            }
        }
        return isFind;
    }

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 5;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String requestURI = request.getRequestURI();
        return !isAllowPath(requestURI);

    }

    @Override
    public Object run() throws ZuulException {
        //获取上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String token = CookieUtils.getCookieValue(request,jwtProperties.getCookieName());

        try {
            JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
        } catch (Exception e) {
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(403);
        }
        return null;
    }
}

当然,有些服务,我们不需要拦截,我们需要设置白名单,并把白名单设置到一个配置类中

@ConfigurationProperties(prefix = "ly.filter")
public class FilterProperties {

    private List<String> allowPaths;

    public List<String> getAllowPaths() {
        return allowPaths;
    }

    public void setAllowPaths(List<String> allowPaths) {
        this.allowPaths = allowPaths;
    }
}

这个地方,我们可以用集合接收

ly:
  jwt:
    pubKeyPath: H:/rsa/rsa.pub # 公钥地址
    cookieName: LY_TOKEN # cookie的名称
  filter:
    allowPaths:
      - /api/auth
      - /api/search
      - /api/user/register
      - /api/user/check
      - /api/user/send
      - /api/item

但是我们最后发现,cookien中并没有token

这是因为zuul内部的敏感头过滤,导致把我们的setCookie和cookie都过滤掉了,zuul中有个默认的过滤器,干了这个事情。

将zuul的敏感头设为null

因为如果我们没法setcookie的话,那么我们就没法向浏览器中写入cookie(响应头)

sensitive-headers:  #敏感头设置为null

nginx和zuul在接收到请求的时候,都会对请求进行转发,转发到我们配置的ip地址上了

我们分别在nginx上和zull上配上我们的配置

nginx

proxy_set_header Host $host ;

zuul

add-host-header: true #携带host本身的请求头信息
我们的目的是让nginx和zull不去修改我们的host


zuul的过滤器

我们让一个类继承zuulFilter就可以实现zuul的过滤器

@Component
@EnableConfigurationProperties(value = {JwtProperties.class, FilterProperties.class})
public class LoginFilter extends ZuulFilter {

    private Logger logger = LoggerFactory.getLogger(LoginFilter.class);

    @Autowired
    private FilterProperties filterProperties;

    @Autowired
    private JwtProperties jwtProperties;

    private Boolean isAllowPath(String uri){
        List<String> allowPaths = filterProperties.getAllowPaths();
        boolean isFind = false;
        for (String allowPath : allowPaths) {
            if (uri.startsWith(allowPath)){
             isFind = true;
             break;
            }
        }
        return isFind;
    }

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 5;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String requestURI = request.getRequestURI();
        return !isAllowPath(requestURI);

    }

    @Override
    public Object run() throws ZuulException {
        //获取上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String token = CookieUtils.getCookieValue(request,jwtProperties.getCookieName());

        try {
            JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
        } catch (Exception e) {
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(403);
        }
        return null;
    }
}



  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值