‌会话管理和身份验证和授权

Cookie、Session、Token


Cookie

简介[Cookie]是一种小型文本文件,由服务器发送到用户的浏览器并保存在用户的计算机上。其主要作用是识别用户身份、跟踪用户活动、保存用户设置等。Cookie通常由名称、值、域名、路径、过期时间等字段组成,并且可以存储在用户的硬盘或内存中‌。

Cookie的定义和作用

Cookie是一种由服务器发送到用户浏览器并保存在用户计算机上的小型文本文件。它主要用于识别用户身份、跟踪用户活动、保存用户设置等。例如,当用户登录一个网站时,服务器会生成一个包含会话ID的Cookie并发送给浏览器,浏览器将这个Cookie保存在本地。此后,每次用户发送请求时,浏览器都会自动将这个Cookie发送给服务器,服务器通过会话ID识别用户身份,从而保持用户的登录状态‌。

Cookie的组成和类型

Cookie通常由以下几个部分组成:

  • - ‌名称(Name):Cookie的名称,一旦创建,名称不可更改。
  • - ‌值(Value):存储的具体数据。
  • - ‌路径(Path):Cookie的使用路径,通常以“/”表示根路径。
  • - ‌过期时间(Expires/Max-Age):Cookie的有效时间,单位为秒。默认情况下,浏览器关闭后Cookie即失效。
  • - ‌域名(Domain):可访问该Cookie的域名。
  • - ‌安全标志(Secure):表示Cookie是否仅通过安全协议传输。

根据用途,Cookie可以分为以下几种类型:

  • - ‌会话Cookie(Session Cookies):在浏览器关闭后即失效。
  • - ‌持久Cookie(Persistent Cookies):存储在用户的硬盘上,直到过期或被删除‌。

Cookie的应用场景和安全性问题

1. 会话管理:用于保持用户的登录状态,通过会话ID识别用户身份。

2. ‌个性化设置:保存用户的个性化设置,如主题、语言、字体大小等。

3. ‌购物车功能:在电子商务网站中保存购物车信息。

4. ‌跟踪用户行为:记录用户在网站上的活动,如访问页面、停留时间、点击链接等。

5. ‌权限验证:作为用户的通行证,验证用户身份。

安全性问题

由于Cookie在HTTP请求中以明文传递,存在安全隐患。为了增强安全性,可以采用以下措施:

  • - ‌加密存储:对敏感信息进行加密处理。
  • - ‌使用安全协议:仅通过安全协议传输Cookie。
  • - ‌设置Secure标志:确保Cookie仅通过安全协议传输‌

 Session

‌[Session]是一种在服务器端存储与特定用户或客户端相关信息的机制。它通过服务器端的一个对象来存储用户的数据,每个Session对象都有一个唯一的ID,这个ID通过[Cookie]的形式发送给客户端。客户端在每次访问时只需将存储有ID的Cookie发回,服务器即可通过这个ID识别客户端并提供相应的个性化服务和数据‌。

Session的基本概念和作用

Session主要用于在网络应用中维持客户端与服务器之间的持久性交互,确保在多个请求之间能够维护客户端的状态信息。它通过会话标识符(Session ID)来唯一标识一个会话,使得在用户会话期间,可以在不同的请求之间共享状态信息‌。Session常用于存储用户的登录信息、购物车内容、用户偏好设置等,提供个性化体验并方便管理用户数据‌。

Session与[Cookie]的关系

Session和Cookie是一对互补的技术。当用户第一次访问网站时,服务器会创建一个Session对象并生成一个唯一的Session ID,然后将这个ID以Cookie的形式发送给客户端。客户端在后续的请求中携带这个Cookie,服务器通过Cookie中的Session ID识别用户,从而实现用户状态的维持和个性化服务的提供‌。虽然Session数据存储在服务器端,但需要通过Cookie来传递Session ID,因此在不支持Cookie的浏览器中,Session将无法正常工作‌。

Session的应用场景和优缺点

‌优点:

  • - ‌个性化体验:提供个性化的服务,如根据用户的偏好展示不同的内容。
  • - ‌方便管理:集中存储用户数据,便于管理和查询。

‌缺点:

  • - ‌性能影响:可能会增加服务器的负担,特别是在高并发情况下。
  • - ‌安全性问题:如果Session ID被截获,可能会导致用户信息泄露或会话劫持。

通过理解Session的基本概念、工作原理以及与Cookie的关系,可以更好地利用这一技术来提升网络应用的用户体验和数据管理效率

 JWT

官网:[JSON Web Token Introduction - jwt.io](https://jwt.io/introduction/)

‌[JWT](JSON Web Token)是一种基于JSON格式的开放标准,用于在网络应用环境间传递声明,特别适用于身份验证和授权。 JWT的设计目的是为了在网络应用环境间安全地传输信息,通常用于[分布式系统]中的身份验证和授权场景,特别是在[单点登录](SSO)和跨域身份验证中‌。

JWT的基本概念和特点

JWT具有以下几个特点:

  • - ‌轻量级:JWT是一个紧凑且自包含的数据格式,可以通过HTTP头或URL参数进行传输‌。
  • - ‌自包含:JWT包含了足够的信息,使得客户端可以判断是否信任该令牌,而不需要查询服务器‌。
  • - ‌可扩展:基于JSON格式,可以自定义Payload中的属性来适应各种业务需求‌。
  • - ‌安全:使用签名来验证发送方和接收方之间的身份,确保消息的机密性和完整性‌。

 JWT的结构

JWT由三个部分组成:头部(Header)、载荷(Payload)和签名(Signature)

1. ‌头部:包含令牌的类型(JWT)和使用的加密算法(如HS256)‌。

   是一个JSON对象,用来描述JWT的元数据

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

   alg属性表示签名的算法(algorithm),默认是 HMAC SHA256 (写成 HS256) ;typ属性表示这个令牌(token)的类型(type),JWT令牌统一写为JWT。 最后,将上面的JSON对象使用Base64URL算法转成字符串。

2. ‌载荷:包含需要传递的数据,如用户身份信息、角色、权限等‌。

​    是一个JSON对象,用来存放实际需要传递的数据,JWT规定了7个官方字段

  • ​    ① iss (issuer):签发人
  • ​    ②exp (expiration time):过期时间
  • ​    ③sub (subject):主题
  • ​    ④nbf (Not Before):生效时间
  • ​    ⑤iat (lssued At):签发时间
  • ​    ⑥jti (JWT ID):编号
  • ​    ⑦aud (audience):受众

​    ‌3.签名:基于头部和载荷的信息,使用秘钥生成的签名,用于验证令牌的真实性和完整性‌

​    Signature部分是对前两部分的签名,防止数据篡改。 首先,需要指定一个密钥 (secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header里面    指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。 HMACSHA256(     base64UrlEncode(header) + ".”"+base64UrlEncode(payload), secret) 算出签名以后,把     Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就    可以返回给用户。

JWT的使用场景和优势

JWT广泛应用于[RESTful API]中,因为它不需要维护任何状态信息。使用JWT可以解决传统[Session]机制在分布式系统中的问题,如session共享和扩展性问题。JWT通过在用户登录时生成一个令牌,客户端保存这个令牌并在每次请求时携带,服务器通过验证令牌来认证用户,从而简化了认证过程并提高了系统的可扩展性‌

 JWT的使用方式

客户端收到服务器返回的JWT,可以储存在Cookie里面,也可以储存在 localStorage。SessionStorage 此后,客户端每次与服务器通信,都要带上这个JWT。你可以把它放在Cookie里面自动发送,但是这样不能跨域,所以更好的做法是放在HTTP请求的头信息Authorization字段里面
引入jar:

```xml
<!--引入jwt的依赖-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>
```

创建jwt的工具类:

public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * 工具类设计思路:
     * ①首先创建JWT令牌的头部信息是确定的-->typ=JWT,alg=HS256
     * ②考虑用户的自定义信息JWT令牌的负载信息-->可以将这一部分使用Map集合封装,通过参数的形式来动态传入
     * 同时负载信息中还有关于token的过期时间,这里可以单独来进行设置(放入参数列表可以进行动态设置)
     * ③考虑JWT签名信息-->使用头部定义的加密算法来实现,设置一个自己的密钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

}

jwt属性类(Properties) 

@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
/**
 * 启用属性类,可在配置文件中配置对应属性
 */
public class JwtProperties {

    /**
     * 管理端员工生成jwt令牌相关配置
     */
    private String adminSecretKey;
    private long adminTtl;
    private String adminTokenName;

    /**
     * 用户端微信用户生成jwt令牌相关配置
     */
    private String userSecretKey;
    private long userTtl;
    private String userTokenName;

}

定义拦截器这里分为两个拦截器(一个是管理者拦截器另一个是员工拦截器)

//===============================管理员登录校验拦截器=====================================
/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {


    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("当前线程;"+Thread.currentThread().getId());

        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            BaseContext.setCurrentId(empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

//===============================员工登录校验拦截器=======================================
/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {


    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("当前线程;"+Thread.currentThread().getId());

        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getUserTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
            Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
            log.info("当前用户id:", userId);
            BaseContext.setCurrentId(userId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

注册拦截器进行使用

/**
 * 配置类,注册web层相关组件
 */
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;

    @Autowired
    private JwtTokenUserInterceptor jwtTokenUserInterceptor;

    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/employee/login");

        registry.addInterceptor(jwtTokenUserInterceptor)
                .addPathPatterns("/user/**")
                .excludePathPatterns("/user/user/login")
                .excludePathPatterns("/user/shop/status");
    }

    /**
     * 通过knife4j生成接口文档
     * @return
     */
    @Bean
    public Docket docket1() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .groupName("管理端接口")
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

    @Bean
    public Docket docket2() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .groupName("用户端接口")
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

    /**
     * 设置静态资源映射
     * @param registry
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    /**
     * 扩展Spring MVC框架的消息转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //需要为消息转换器设置一个对象转换器
        converter.setObjectMapper(new JacksonObjectMapper());
        //将自己的消息转换器加入到容器里面
        converters.add(0,converter);
    }
}

cotroller层相关业务代码

/**
 * 员工管理
 */
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")
    @ApiOperation(value = "员工登录")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值