日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Security +Spring Session Redis+JJWT

發布時間:2023/12/20 javascript 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Security +Spring Session Redis+JJWT 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

重要提示

這樣集成弄完一波后,導致Spring Security并發控制并沒有生效,請大佬們慎重參考下面內容。

問題

希望使用Spring Security對Spring Boot進行保護,并且,使用Spring Session Redis來進行集中會話管理,能夠將JWT保存到會話中。這里的做法將JWT種到session中,而不是種到Cookies中,以保證JWT不會暴露到前端去。

一圖勝千言

步驟

pom.xml

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.0</version></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred --><version>0.11.5</version><scope>compile</scope></dependency><dependency><groupId>org.modelmapper.extensions</groupId><artifactId>modelmapper-spring</artifactId><version>3.1.1</version></dependency></dependencies>

統一返回VO

Result.java

package com.example.demo.comm;import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.http.HttpStatus;@Builder @Data @NoArgsConstructor @AllArgsConstructor public class Result {@Builder.Defaultprivate int code = HttpStatus.OK.value();private String message;private Object data; }

角色種類

RoleEnum.java

package com.example.demo.comm;import lombok.AllArgsConstructor; import lombok.Getter;@Getter @AllArgsConstructor public enum RoleEnum {ADMIN("ADMIN", "超級管理員"),USER("USER", "普通用戶");private final String code;private final String name;public static RoleEnum getByCode(String code){for (RoleEnum value : values()) {if (value.getCode().equals(code)) {return value;}}return null;} }

這里就2種角色。

通用異常處理類

DemoException.java

package com.example.demo.exception;public class DemoException extends RuntimeException{public DemoException(String message){super(message);} }

GlobalExceptionTranslator.java——Spring全局異常類

package com.example.demo.exception;import com.example.demo.comm.Result; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.hibernate.validator.internal.engine.path.PathImpl; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.Set;/*** 全局異常*/ @Slf4j @RestControllerAdvice public class GlobalExceptionTranslator {@ExceptionHandler(DemoException.class)public ResponseEntity<Result> recsException(DemoException demoException){Result result = Result.builder().code(HttpStatus.INTERNAL_SERVER_ERROR.value()).message(demoException.getMessage()).build();return ResponseEntity.ok().body(result);}@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<Result> handleError(MethodArgumentNotValidException e) {log.warn("Method Argument Not Valid", e);BindingResult result = e.getBindingResult();FieldError error = result.getFieldError();String message = null;if (error != null) {message = String.format("%s:%s", error.getField(), error.getDefaultMessage());}return ResponseEntity.badRequest().body(Result.builder().code(HttpStatus.BAD_REQUEST.value()).message(message).build());}@ExceptionHandler(ConstraintViolationException.class)public ResponseEntity<Result> handleError(ConstraintViolationException e) {log.warn("Constraint Violation", e);Set<ConstraintViolation<?>> violations = e.getConstraintViolations();ConstraintViolation<?> violation = violations.iterator().next();String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();String message = String.format("%s:%s", path, violation.getMessage());return ResponseEntity.badRequest().body(Result.builder().code(HttpStatus.BAD_REQUEST.value()).message(message).build());}}

Spring的全局異常類中,注冊DemoException類,不然,在業務代碼里面拋異常會被Spring Security捕獲。

Model層

實體層設計思路:主要是User用戶表和Role角色表,加上UserRole中間用戶角色關系表。

SQL

create table user (id bigint auto_increment comment '主表id'primary key,username varchar(200) not null unique comment '用戶名是唯一的',nickname varchar(200) null comment '別名',password varchar(200) not null comment '密碼',email varchar(200) null comment '電子郵箱',deleted tinyint(1) default 0 comment '0 未刪除 1 已刪除' )comment '用戶' charset = utf8mb4;create table role (id bigint auto_increment comment '主表id'primary key,name varchar(200) not null comment '名稱',code varchar(50) not null unique comment '編碼是唯一的' )comment '角色' charset = utf8mb4;create table user_role (id bigint auto_increment comment '主表id'primary key,user_id bigint not null comment '用戶id',role_id bigint not null comment '角色id' )comment '用戶與角色關系表' charset = utf8mb4;

User.java

package com.example.demo.model;import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;@Builder @Data @NoArgsConstructor @AllArgsConstructor public class User {/*** 用戶id*/private Long id;/*** 用戶名 唯一的*/private String username;/*** 昵稱*/private String nickname;private String password;private String email;private boolean deleted = false; }

Role.java

package com.example.demo.model;import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;@Builder @Data @NoArgsConstructor @AllArgsConstructor public class Role {/*** 角色id*/private Long id;private String name;/*** 角色編碼 唯一*/private String code; }

UserRole.java

package com.example.demo.model;import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;@Builder @Data @NoArgsConstructor @AllArgsConstructor public class UserRole {private Long id;/*** 用戶id* @see com.example.demo.model.User*/private Long userId;/*** 用戶角色id* @see com.example.demo.model.Role*/private Long roleId; }

DAO層

UserMapper.java

package com.example.demo.mapper;import com.example.demo.model.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param;@Mapper public interface UserMapper {int insertUser(@Param("user") User user);User findUserByUsername(@Param("username") String username); }

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.example.demo.mapper.UserMapper"><resultMap id="BaseResultMap" type="com.example.demo.model.User"><id column="id" property="id" jdbcType="BIGINT" javaType="java.lang.Long"/><result property="username" column="username" jdbcType="VARCHAR" javaType="java.lang.String"/><result property="nickname" column="nickname" jdbcType="VARCHAR" javaType="java.lang.String"/><result property="password" column="password" jdbcType="VARCHAR" javaType="java.lang.String"/><result property="email" column="email" jdbcType="VARCHAR" javaType="java.lang.String"/><result property="deleted" column="deleted" jdbcType="TINYINT" javaType="java.lang.Boolean" /></resultMap><sql id="BaseColumns">id, username, nickname, password, email, deleted</sql><insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into user (username, nickname, password, email, deleted)values (#{user.username}, #{user.nickname}, #{user.password}, #{user.email}, 0)</insert><select id="findUserByUsername" resultMap="BaseResultMap">select <include refid="BaseColumns"/> from user where username = #{username}</select> </mapper>

RoleMapper.java

package com.example.demo.mapper;import com.example.demo.model.Role; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param;import java.util.List;@Mapper public interface RoleMapper {int insertRole(@Param("role") Role role);Role findRoleByCode(@Param("code") String code);/*** 根據用戶id查角色* @param userId 用戶id* @return 角色*/List<Role> findRoleByUserId(@Param("userId") Long userId); }

RoleMapper.xml

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.example.demo.mapper.RoleMapper"><resultMap id="BaseResultMap" type="com.example.demo.model.Role"><id column="id" property="id" jdbcType="BIGINT" javaType="java.lang.Long"/><result property="name" column="name" jdbcType="VARCHAR" javaType="java.lang.String"/><result property="code" column="code" jdbcType="VARCHAR" javaType="java.lang.String"/></resultMap><sql id="BaseColumns">id, name, code</sql><sql id="BaseJoinColumns">role.id, role.name, role.code</sql><insert id="insertRole" useGeneratedKeys="true" keyProperty="id">insert into role (name, code)values (#{role.name}, #{role.code})</insert><select id="findRoleByCode" resultMap="BaseResultMap">select <include refid="BaseColumns"/> from role where code = #{code}</select><select id="findRoleByUserId" resultMap="BaseResultMap">select <include refid="BaseJoinColumns"/> from roleinner join user_roleon user_role.user_id = #{userId} and user_role.role_id = role.id</select></mapper>

UserRoleMapper.java

package com.example.demo.mapper;import com.example.demo.model.UserRole; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param;@Mapper public interface UserRoleMapper {int insertUserRole(@Param("userRole") UserRole userRole);UserRole findUserRoleByUserIdAndRoleId(@Param("userId") Long userId, @Param("roleId") Long roleId); }

UserRoleMapper.xml

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.example.demo.mapper.UserRoleMapper"><resultMap id="BaseResultMap" type="com.example.demo.model.UserRole"><id column="id" property="id" jdbcType="BIGINT" javaType="java.lang.Long"/><result property="roleId" column="role_id" jdbcType="BIGINT" javaType="java.lang.Long"/><result property="userId" column="user_id" jdbcType="BIGINT" javaType="java.lang.Long"/></resultMap><sql id="BaseColumns">id, role_id, user_id</sql><insert id="insertUserRole" useGeneratedKeys="true" keyProperty="id">insert into user_role (user_id, role_id)values (#{userRole.userId}, #{userRole.roleId})</insert><select id="findUserRoleByUserIdAndRoleId" resultMap="BaseResultMap">select <include refid="BaseColumns"/> from user_role where user_id = #{userId} and role_id = #{roleId}</select></mapper>

VO層

UserReq.java

package com.example.demo.vo;import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;@Builder @Data @NoArgsConstructor @AllArgsConstructor public class UserReq {/*** 用戶名 唯一的*/@NotEmpty(message = "用戶名不能為空")private String username;/*** 昵稱*/@NotEmpty(message = "昵稱不能為空")private String nickname;@NotEmpty(message = "密碼不能為空")@Size(min = 8, message = "至少為8個字符")@Size(max = 20, message = "最多只能是20個字符")private String password;@Emailprivate String email; }

UserReq主要用于新用戶注冊,即創建新用戶。

UserRes.java

package com.example.demo.vo;import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;@Builder @Data @NoArgsConstructor @AllArgsConstructor public class UserRes {/*** 用戶id*/private Long id;/*** 用戶名 唯一的*/private String username;/*** 昵稱*/private String nickname;private String email;private boolean deleted = false; }

注冊用戶成功后返回的vo類。

RoleReq.java

package com.example.demo.vo;import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;@Builder @Data @NoArgsConstructor @AllArgsConstructor public class RoleReq {private String name;/*** 角色編碼 唯一*/private String code; }

添加角色時需要的RoleReq類。

LoginReq.java

package com.example.demo.vo;import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;@Builder @Data @NoArgsConstructor @AllArgsConstructor public class LoginReq {private String username;private String password; }

登錄時的請求vo。

UserInfoRes.java

package com.example.demo.vo;import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;import java.util.List;@Builder @Data @NoArgsConstructor @AllArgsConstructor public class UserInfoRes {private String username;private List<String> roles; }

Service層

UserService.java

主要實現創建用戶接口,也就是注冊用戶接口和從Spring Security中獲取當前用戶的email數據接口。

package com.example.demo.service;import com.example.demo.comm.Result; import com.example.demo.vo.UserReq; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication;public interface UserService {/*** 注冊新用戶* @param req 用戶* @return 新用戶*/ResponseEntity<Result> register(UserReq req);String getEmail(Authentication authentication); }

UserServiceImp.java

package com.example.demo.service.imp;import com.example.demo.comm.Result; import com.example.demo.comm.RoleEnum; import com.example.demo.comm.UserDetailsImpl; import com.example.demo.exception.DemoException; import com.example.demo.mapper.RoleMapper; import com.example.demo.mapper.UserMapper; import com.example.demo.mapper.UserRoleMapper; import com.example.demo.model.Role; import com.example.demo.model.User; import com.example.demo.model.UserRole; import com.example.demo.service.UserService; import com.example.demo.vo.UserReq; import com.example.demo.vo.UserRes; import jakarta.annotation.Resource; import org.modelmapper.ModelMapper; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;@Service public class UserServiceImp implements UserService {@Resourceprivate UserMapper userMapper;@Resourceprivate ModelMapper modelMapper;@Resourceprivate RoleMapper roleMapper;@Resourceprivate UserRoleMapper userRoleMapper;@Resourceprivate PasswordEncoder encoder;@Transactional(rollbackFor = Exception.class)@Overridepublic ResponseEntity<Result> register(UserReq req) {// 判斷用戶是否已經存在User oldUser = userMapper.findUserByUsername(req.getUsername());if (oldUser != null) {throw new DemoException(req.getUsername() + "用戶名已經存在");}// 密碼加密req.setPassword(encoder.encode(req.getPassword()));User user = modelMapper.map(req, User.class);// 注冊用戶userMapper.insertUser(user);// 查詢普通用戶角色Role role = roleMapper.findRoleByCode(RoleEnum.USER.getCode());if (role == null) {throw new DemoException(RoleEnum.USER.getCode() + "角色不存在");}// 查詢是否存在普通用戶關系UserRole userRole = userRoleMapper.findUserRoleByUserIdAndRoleId(user.getId(), role.getId());// 添加普通用戶角色關系if (userRole == null) {userRoleMapper.insertUserRole(UserRole.builder().userId(user.getId()).roleId(role.getId()).build());}// 返回新用戶Result result = Result.builder().data(modelMapper.map(user, UserRes.class)).build();return ResponseEntity.ok().body(result);}@Overridepublic String getEmail(Authentication authentication) {UserDetailsImpl userDetailsImpl= (UserDetailsImpl) authentication.getPrincipal();return userDetailsImpl.getEmail();} }

RoleService.java

package com.example.demo.service;import com.example.demo.model.Role; import com.example.demo.vo.RoleReq;import java.util.List;public interface RoleService {List<Role> findByUserId(Long userId);Role addRole(RoleReq req); }

RoleServiceImp.java

package com.example.demo.service.imp;import com.example.demo.exception.DemoException; import com.example.demo.mapper.RoleMapper; import com.example.demo.model.Role; import com.example.demo.service.RoleService; import com.example.demo.vo.RoleReq; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils;import java.util.Collections; import java.util.List;@Service @Slf4j public class RoleServiceImp implements RoleService {@Resourceprivate RoleMapper roleMapper;@Resourceprivate ModelMapper modelMapper;@Overridepublic List<Role> findByUserId(Long userId) {List<Role> roles = roleMapper.findRoleByUserId(userId);if (CollectionUtils.isEmpty(roles)){return Collections.emptyList();}return roles;}@Overridepublic Role addRole(RoleReq req) {Role oldRole = roleMapper.findRoleByCode(req.getCode());if (oldRole != null){throw new DemoException("角色已經存在");}Role role = modelMapper.map(req, Role.class);roleMapper.insertRole(role);return role;} }

這個類主要實現了角色根據id查找和角色創建。

Controller層

  • /auth/login:登錄接口
  • /auth/logout:登出接口
  • /role/add:添加角色接口
  • /user/register:添加用戶接口
  • /user/greetings:驗證接口

AuthController.java

package com.example.demo.controller;import com.example.demo.comm.JwtUtils; import com.example.demo.comm.Result; import com.example.demo.comm.UserDetailsImpl; import com.example.demo.vo.LoginReq; import com.example.demo.vo.UserInfoRes; import com.fasterxml.jackson.core.JsonProcessingException; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority;; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.context.SecurityContextRepository;import java.util.List; @Slf4j @RestController @RequestMapping("/auth") public class AuthController {@Resourceprivate AuthenticationManager authenticationManager;@Resourceprivate SecurityContextRepository securityContextRepository;private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();@Resourceprivate JwtUtils jwtUtils;@PostMapping("/login")public ResponseEntity<Result> authenticateUser(@RequestBody LoginReq loginRequest, HttpSession httpSession) throws JsonProcessingException {UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(loginRequest.getUsername(), loginRequest.getPassword());Authentication authentication = authenticationManager.authenticate(token);SecurityContext context = securityContextHolderStrategy.createEmptyContext();context.setAuthentication(authentication);securityContextRepository.saveContext(context, request, response);log.info("會話id:" + httpSession.getId());UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();jwtUtils.generateJwtCookie(httpSession, userDetails);List<String> roles = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();UserInfoRes userInfoResponse = UserInfoRes.builder().username(userDetails.getUsername()).roles(roles).build();return ResponseEntity.ok().body(Result.builder().data(userInfoResponse).build());}@PostMapping("/logout")public ResponseEntity<Result> logout(HttpSession session) {session.invalidate();return ResponseEntity.ok(Result.builder().build());} }

RoleController.java

package com.example.demo.controller;import com.example.demo.comm.Result; import com.example.demo.model.Role; import com.example.demo.service.RoleService; import com.example.demo.vo.RoleReq; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.annotation.RequestScope;@Slf4j @RestController @RequestScope @RequestMapping("/role") public class RoleController {@Resourceprivate RoleService roleService;@PostMapping("/add")public ResponseEntity<Result> addRole(@RequestBody RoleReq req){Role role = roleService.addRole(req);Result result = Result.builder().data(role).build();return ResponseEntity.ok().body(result);} }

UserController.java

package com.example.demo.controller;import com.example.demo.comm.Result; import com.example.demo.service.UserService; import com.example.demo.vo.UserReq; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("/user") public class UserController {@Resourceprivate UserService userService;@PostMapping("/register")public ResponseEntity<Result> register(@Valid @RequestBody UserReq req){return userService.register(req);}@GetMapping("/greetings")public String greetings(Authentication authentication) {String email = userService.getEmail(authentication);return "Hello World " + email;} }

JJWT

JwtConfig.java

package com.example.demo.config;import org.springframework.context.annotation.Configuration;@Configuration public class JwtConfig {public static final String userKey = "user"; }

JwtUtils.java——JWT核心類

package com.example.demo.comm;import com.example.demo.config.JwtConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.context.annotation.RequestScope;import java.security.Key; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set;@Component @RequestScope @Slf4j public class JwtUtils {@Value("${app.cookie.jwt.secret}")private String jwtSecret;@Value("${app.cookie.jwt.expiration}")private int jwtExpirationMs;@Value("${app.cookie.jwt.name}")private String jwtCookie;@Resourceprivate ObjectMapper objectMapper;public void generateJwtCookie(HttpSession httpSession, UserDetailsImpl userPrincipal) throws JsonProcessingException {String jwt = generateTokenFromUsername(userPrincipal);httpSession.setAttribute(jwtCookie, jwt);}private Key getKey(){byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);return Keys.hmacShaKeyFor(keyBytes);}public String generateTokenFromUsername(UserDetailsImpl userPrincipal) throws JsonProcessingException {Key key = this.getKey();Map<String,Object> claims = new HashMap<>();claims.put(JwtConfig.userKey, objectMapper.writeValueAsString(userPrincipal));return Jwts.builder().setSubject(userPrincipal.getUsername()).setClaims(claims).setIssuedAt(new Date()).setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)).signWith(key, SignatureAlgorithm.HS512).compact();}public String getJwtFromCookies(HttpServletRequest request) {HttpSession httpSession = request.getSession();String jwt = (String) httpSession.getAttribute(jwtCookie);if (StringUtils.hasText(jwt)){return jwt;} else {return null;}}public boolean validateJwtToken(String authToken) {try {Key key = this.getKey();Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authToken);return true;} catch (MalformedJwtException e) {log.error("Invalid JWT token: {}", e.getMessage());} catch (ExpiredJwtException e) {log.error("JWT token is expired: {}", e.getMessage());} catch (UnsupportedJwtException e) {log.error("JWT token is unsupported: {}", e.getMessage());} catch (IllegalArgumentException e) {log.error("JWT claims string is empty: {}", e.getMessage());}return false;}public UserDetailsImpl getUserNameFromJwtToken(String token) throws JsonProcessingException {Key key = this.getKey();String json = (String) Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().get(JwtConfig.userKey);SimpleModule module = new SimpleModule("GrantedAuthority");String moduleName = module.getModuleName();module.addDeserializer(GrantedAuthority.class, new GrantedAuthorityDeser());Set<Object> registeredModuleIds = objectMapper.getRegisteredModuleIds();boolean isRegistered = false;for (Object registeredModuleId : registeredModuleIds) {isRegistered = registeredModuleId.equals(moduleName);if (isRegistered) {break;}}if (!isRegistered) {objectMapper.registerModule(module);}return objectMapper.readValue(json, UserDetailsImpl.class);} }

這個JwtUtils類是JWT種到Session的核心實現類:

  • httpSession.setAttribute(jwtCookie, jwt);:這行就是在session中種JWT;
  • claims.put(JwtConfig.userKey, objectMapper.writeValueAsString(userPrincipal));:將用戶數據寫到JWT中;
  • public UserDetailsImpl getUserNameFromJwtToken(String token):從JWT 中解析出當前用戶。

GrantedAuthorityDeser.java

package com.example.demo.comm;import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;import java.io.IOException;public class GrantedAuthorityDeser extends StdDeserializer<GrantedAuthority> {public GrantedAuthorityDeser(){this(null);}public GrantedAuthorityDeser(Class<?> vc) {super(vc);}@Overridepublic GrantedAuthority deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {JsonNode node = p.getCodec().readTree(p);String role = node.get("authority").asText();return new SimpleGrantedAuthority(role);} }

自定義教jackson將json字符串序列化成GrantedAuthority接口類,因為接口類沒有構造器。

Spring Security

這里的Spring Security使用的Form login方式進行登錄的,在SecurityConfiguration.java核心類中,可以看到相關配置。

AuthEntryPointJwt.java

package com.example.demo;import com.example.demo.comm.Result; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component;import java.io.IOException;@Slf4j @Component public class AuthEntryPointJwt implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {log.error("Unauthorized error: {}", authException.getMessage());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);Result body = Result.builder().code(HttpServletResponse.SC_UNAUTHORIZED).message(authException.getMessage()).build();ObjectMapper mapper = new ObjectMapper();mapper.writeValue(response.getOutputStream(), body);} }

當出現Spring Security驗證失敗時,自定義處理。默認情況,Spring Security是要前端瀏覽器跳轉login.html頁面。如下圖:

這是Spring Security官網的關于Form登錄方式的異常場景的流程圖,這里我們就是使用AuthEntryPointJwt替代了LoginUrlAuthenticationEntryPoint,具體配置參考SecurityConfiguration.java核心類。

特殊的UserDetailsImpl——從SpringSecurity中獲到當前用戶

package com.example.demo.comm;import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection; import java.util.Objects;public class UserDetailsImpl implements UserDetails {private Long id;private String username;private String email;private String nickname;private boolean enabled;@JsonIgnoreprivate String password;private Collection<? extends GrantedAuthority> authorities;public UserDetailsImpl(Long id, String username, String nickname, String email, String password, boolean enabled,Collection<? extends GrantedAuthority> authorities) {this.id = id;this.username = username;this.nickname = nickname;this.email = email;this.password = password;this.enabled = enabled;this.authorities = authorities;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return enabled;}public Long getId() {return id;}public String getEmail() {return email;}public String getNickname(){return nickname;}@Overridepublic boolean equals(Object o) {if (this == o)return true;if (o == null || getClass() != o.getClass())return false;UserDetailsImpl user = (UserDetailsImpl) o;return Objects.equals(id, user.id);} }

這個類是使用Spring Security從會話中獲取到當前用戶類,也就意味著JWT序列化保存到用戶類就是這個UserDetailsImpl類,其中序列化的時候忽略用戶密碼。

特殊的UserDetailsServiceImp

package com.example.demo.service.imp;import com.example.demo.comm.UserDetailsImpl; import com.example.demo.mapper.UserMapper; import com.example.demo.model.Role; import com.example.demo.model.User; import com.example.demo.service.RoleService; import jakarta.annotation.Resource; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service;import java.util.List;@Service public class UserDetailsServiceImp implements UserDetailsService {@Resourceprivate UserMapper userMapper;@Resourceprivate RoleService roleService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userMapper.findUserByUsername(username);if (user == null) {throw new UsernameNotFoundException("User Not Found with username: " + username);}// 查詢角色List<Role> roles = roleService.findByUserId(user.getId());List<SimpleGrantedAuthority> authorities = roles.stream().map(role -> new SimpleGrantedAuthority(role.getCode())).toList();return new UserDetailsImpl(user.getId(), username,user.getNickname(), user.getEmail(), user.getPassword(),!user.isDeleted(),authorities);} }

Spring Security一般默認使用UserDetailsService類的bean進行用戶驗證。具體在SecurityConfiguration.java核心類中,進行了顯示配置。

SecurityConfiguration.java核心類

package com.example.demo.config;import com.example.demo.AuthEntryPointJwt; import com.example.demo.comm.RoleEnum; import com.example.demo.service.imp.UserDetailsServiceImp; import jakarta.annotation.Resource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.session.HttpSessionEventPublisher; import org.springframework.security.web.context.DelegatingSecurityContextRepository; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository;import static org.springframework.security.config.Customizer.withDefaults;@Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfiguration {@Resourceprivate UserDetailsServiceImp userDetailsService;@Resourceprivate AuthEntryPointJwt unauthorizedHandler;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic DaoAuthenticationProvider authenticationProvider() {DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();authProvider.setUserDetailsService(userDetailsService);authProvider.setPasswordEncoder(passwordEncoder());return authProvider;}@Beanpublic HttpSessionEventPublisher httpSessionEventPublisher() {return new HttpSessionEventPublisher();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and().securityContext(securityContext -> securityContext.securityContextRepository(securityContextRepository())).authorizeHttpRequests(authorize -> authorize.requestMatchers(HttpMethod.GET, "/user/greetings").hasAuthority(RoleEnum.ADMIN.getCode()).requestMatchers(HttpMethod.POST, "/auth/login", "/role/add", "/user/register").permitAll().anyRequest().authenticated()).authenticationProvider(authenticationProvider()).formLogin(withDefaults()).httpBasic().disable();return http.build();}@Beanpublic SecurityContextRepository securityContextRepository(){return new DelegatingSecurityContextRepository(new RequestAttributeSecurityContextRepository(),new HttpSessionSecurityContextRepository());}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {return authConfig.getAuthenticationManager();} }

Spring配置

application.yml

spring:application:name: demosession:redis:flush-mode: on_savenamespace: demo:sessiontimeout: P30Ddatasource:driver-class-name: com.mysql.cj.jdbc.Driverjackson:deserialization:# 忽略json中多余字段fail-on-unknown-properties: false server:port: 8080servlet:session:cookie:same-site: strictsecure: truehttp-only: truepath: ${spring.mvc.servlet.path} app:cookie:jwt:name: ${spring.application.name}expiration: 86400000 # 單位是毫秒secret: zhouShippinglsosmdlfjhkashjhgkfggdfgxxxxxdsfawdfaslsosmdlfjhkaslflahlhasjfghlasdjlhfzhouShippinglsosmdlfjhkaslflahlhasjfghlasdjlhf mybatis:mapper-locations: classpath:/mapper/*.xml

application-dev.yml

spring:data:redis:host: 00.xxx.xxx.xxxport: 6379password: ${REDIS_PW}database: 0datasource:url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/demo?sslMode=REQUIRED&characterEncoding=UTF-8&connectionTimeZone=GMT%2B8&forceConnectionTimeZoneToSession=trueusername: ${MYSQL_USERNAME}password: ${MYSQL_PW}

測試

登錄接口


登錄成功后,獲取到SESSION的ID,給下面接口使用:

上面的成功登錄后,調用獲取資源接口。值得注意的是,第一次使用登錄成功后的會話,Spring Security會復制一個相同會話給前端進行使用。

總結

Spring Security對REST支持一般,只是提供了比較好的規范。這里感覺就是把Spring Security的Form登錄給架空了。
源代碼:https://github.com/fxtxz2/JwtSession

參考:

  • Spring Boot Login example: Rest API with MySQL and JWT
  • Form Login
  • Architecture
  • Introduction to Spring Method Security
  • jjwt
  • Spring Boot Security Role-based Authorization Tutorial

總結

以上是生活随笔為你收集整理的Spring Security +Spring Session Redis+JJWT的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

中文国产字幕在线观看 | 最新的av网站 | 亚洲欧美视频 | 国产五十路毛片 | 亚洲成年人在线播放 | 国产一区国产二区在线观看 | 91精品免费在线视频 | 欧美激情h | 色婷婷啪啪免费在线电影观看 | 日韩在线观看视频一区二区三区 | 黄色一级影院 | 超级碰碰碰视频 | 五月天亚洲激情 | 久久久久久免费网 | 日韩免费视频观看 | 三级av网| 国产精品一区久久久久 | 蜜臀久久99精品久久久无需会员 | 人人澡视频| 婷婷视频导航 | 色夜视频| 日韩综合一区二区三区 | 亚洲aⅴ一区二区三区 | 久久久久电影 | 91精品系列 | 97超碰香蕉 | 五月婷婷综合激情 | 最近中文字幕高清字幕在线视频 | 久久久久久久久久久久av | 区一区二区三区中文字幕 | 草草草影院| 久久久久久久av麻豆果冻 | 女人18毛片90分钟 | 亚洲成人av在线 | 99视频精品视频高清免费 | 欧美一级电影免费观看 | 色偷偷网站视频 | 人人揉人人揉人人揉人人揉97 | 欧美在线1| 九七视频在线 | 精品在线观 | av免费在线网站 | 五月婷网站 | 一级黄色网址 | 国产精品日韩在线播放 | 欧洲高潮三级做爰 | 99综合电影在线视频 | 久热国产视频 | 久草在线在线 | 青青久草在线 | 在线观看www91| 91免费视频国产 | 中文字幕免费在线看 | 国产视频亚洲精品 | 欧美了一区在线观看 | av超碰在线 | 天天操天天操天天操 | 国产黄色精品 | 天天躁日日躁狠狠躁av麻豆 | 国产亚洲精品久久网站 | 国产精品一区在线观看你懂的 | 天天操综合 | 亚洲三级网站 | 日韩在线无 | 久草网视频在线观看 | 天干啦夜天干天干在线线 | 久久久www成人免费毛片麻豆 | 五月婷婷六月丁香 | 国产精品成人免费一区久久羞羞 | 在线观看视频你懂得 | 在线亚洲人成电影网站色www | 狠狠色丁香久久婷婷综合_中 | 国产 日韩 中文字幕 | 中文字幕视频观看 | 2023年中文无字幕文字 | 欧美日韩另类视频 | 深夜免费福利在线 | 日韩中文字幕在线看 | 成人免费 在线播放 | www日韩精品 | 日日夜色 | 日韩大片在线观看 | a视频在线 | 精品综合久久久 | 国产精品久久久久久影院 | 狠狠综合久久av | 91九色精品女同系列 | 成年人免费看 | 久操操 | 亚洲国产视频在线 | 日韩高清观看 | 中文字幕一区二区三区乱码在线 | 青青河边草免费直播 | 中文字幕成人在线观看 | 国际精品久久久 | 婷婷成人亚洲综合国产xv88 | 国产精品久久久久av福利动漫 | 亚洲精选国产 | 国产精品福利午夜在线观看 | 国产中的精品av小宝探花 | 成人动图 | 911久久| 黄网站色视频 | 日本精品一区二区三区在线播放视频 | 字幕网av | 奇米影视8888在线观看大全免费 | 欧美久久久久久久久久 | 久久视频免费在线观看 | 婷婷成人亚洲综合国产xv88 | 欧美日韩国产综合网 | 成年人视频在线免费 | 国内少妇自拍视频一区 | 黄色三级免费网址 | 国产一级在线 | 久草在线这里只有精品 | 337p西西人体大胆瓣开下部 | 91精品免费在线观看 | 麻豆精品91 | 国产免费亚洲 | 色婷婷色| 超碰九九| 91日韩国产| 成人av手机在线 | 国产成人精品午夜在线播放 | 久久国产精品小视频 | 国产日韩欧美在线观看 | 国产亚洲精品美女 | 99免费精品 | 亚洲天堂va | 91在线永久 | 精品一区二区三区四区在线 | 欧美日韩国产精品一区二区三区 | 99视频在线观看免费 | 天天干天天干天天干天天干天天干天天干 | 天天色综合天天 | 天天亚洲 | 天天色播| 亚洲一级二级 | 国产97视频在线 | 在线视频精品 | 91精品国产欧美一区二区 | 欧美亚洲成人xxx | 成人av电影在线播放 | 美女免费视频一区二区 | 最近中文字幕高清字幕在线视频 | 久草视频在线免费播放 | 欧美日韩中字 | 久久蜜臀一区二区三区av | 久久久久久99精品 | 伊人久久五月天 | 国产高清在线看 | 国产精品s色 | 亚洲国产视频直播 | 亚洲国产片 | www.久久精品视频 | 色婷婷骚婷婷 | 在线观看视频福利 | 国产一区二区三区在线 | 婷婷久久亚洲 | 久久久久一区二区三区 | 久久久久国产精品免费网站 | 夜夜操天天干 | 亚洲va男人天堂 | 91香蕉国产 | 国产精品你懂的在线观看 | 99国产免费网址 | 午夜精品视频在线 | 国产小视频你懂的在线 | 在线观看91精品国产网站 | 免费在线观看av的网站 | 久久国产综合视频 | 亚洲精品久久久蜜桃直播 | 亚洲专区在线播放 | 高清不卡免费视频 | 天天色影院 | 日韩视频在线不卡 | 成人资源在线观看 | 国产精品资源在线观看 | 国产色女 | 夜夜高潮夜夜爽国产伦精品 | 日p在线观看 | 99久久久国产精品免费99 | 天天色天天操天天爽 | 在线成人高清电影 | 久草在线视频在线观看 | 久久免费视频5 | 久久久久久久久久久久久久免费看 | 精品久久久久国产免费第一页 | 在线午夜av | 色噜噜狠狠狠狠色综合久不 | 啪嗒啪嗒免费观看完整版 | 国产免费人人看 | 99视频免费播放 | 在线国产日韩 | 亚洲草视频 | 青春草免费视频 | 亚洲视频一区二区三区在线观看 | 91日韩在线专区 | 国产一在线精品一区在线观看 | 在线国产福利 | 久久视屏网 | 香蕉视频在线看 | 天天射天天干天天操 | 69国产盗摄一区二区三区五区 | 黄色av高清 | www.亚洲视频.com | 亚洲欧美日韩精品久久久 | 亚洲视频一区二区三区在线观看 | 日韩最新av | 久草视频在线资源 | 成人片在线播放 | 18国产精品白浆在线观看免费 | 激情九九 | 五月婷婷在线播放 | 久久a久久 | 欧美性大战 | 午夜精品婷婷 | 视频在线观看99 | 五月天久久狠狠 | 午夜性盈盈 | 亚洲成人免费观看 | 亚洲午夜精品久久久 | 免费午夜av| 久久精品国产第一区二区三区 | 久久久www成人免费毛片麻豆 | 在线日韩三级 | 亚洲激情中文 | 亚洲精品456在线播放乱码 | 香蕉网站在线观看 | 天天操夜 | 成人亚洲精品久久久久 | 欧美在线不卡一区 | 久久亚洲美女 | 精品国产一区二区三区不卡 | 精品国产精品久久 | 精品一区二区在线免费观看 | 成人免费看片98欧美 | 午夜视频在线观看一区 | 久久高清国产视频 | 免费毛片一区二区三区久久久 | 91成年人视频 | 精品欧美一区二区在线观看 | 中文字幕在线观看一区二区三区 | 欧美精品久久99 | 黄色影院在线观看 | 在线最新av | 开心丁香婷婷深爱五月 | www.久久色.com | 天天综合导航 | 亚洲视频在线播放 | 国产一级黄色免费看 | 人人射人人爱 | 91久色蝌蚪 | 亚洲精品视频一二三 | 欧洲精品在线视频 | 九九热精品视频在线播放 | 欧美日韩在线观看一区二区三区 | 在线免费黄色毛片 | 欧美精品久久久久久久久久丰满 | 91av大全| 欧美a免费 | 国产中文字幕视频在线 | 色婷婷视频在线观看 | 免费a网站| 国产色在线视频 | 久久久久久久久久久福利 | 国产一区二区三区黄 | 国产精品嫩草影院123 | 一区二区三区日韩视频在线观看 | 免费视频一区 | 亚洲天堂免费视频 | 欧美 日韩 视频 | 99在线精品视频观看 | 97人人超碰在线 | av网站免费在线 | 国产视频精品免费播放 | 欧美日韩国产精品一区二区 | 91视频高清 | 西西www4444大胆视频 | 午夜精品99久久免费 | 五月婷久 | 精品免费在线视频 | 五月婷婷亚洲 | 亚洲精品美女久久久久 | 亚洲 综合 精品 | 97av超碰| 久久精品国产亚洲精品2020 | 人人狠 | 中文字幕成人在线观看 | www.色com| 天天操天| 国产高清精品在线观看 | 国产在线小视频 | 欧美成人区 | 四虎影视成人永久免费观看视频 | 日韩av中文字幕在线 | 国产在线探花 | 久久免费视频8 | 欧美一区日韩一区 | 少妇bbbb | 国产成人免费网站 | 超碰在线中文字幕 | 国产精品日韩欧美一区二区 | 国产福利午夜 | 亚洲精品a区 | 亚洲一级电影 | 亚洲观看黄色网 | 国产精品日韩久久久久 | 国产精品美女999 | 96精品高清视频在线观看软件特色 | 国产乱码精品一区二区蜜臀 | 9999精品免费视频 | 久久国产成人午夜av影院潦草 | 久久国产二区 | 一级一片免费观看 | 国产精品日韩高清 | 国产精品久久精品国产 | 日日干av | 99精品国产高清在线观看 | 久久久av免费| 国产精品第 | 永久免费的啪啪网站免费观看浪潮 | 亚洲精品伦理在线 | 免费看一级黄色大全 | 91av在线视频播放 | 久草视频免费看 | 日韩精品在线看 | 91精品国产福利在线观看 | 99视频网址| 96精品高清视频在线观看软件特色 | 黄网站免费久久 | 国产在线a| 成人全视频免费观看在线看 | 四虎成人精品永久免费av | 五月天中文在线 | 日韩二区精品 | 免费在线观看日韩 | 91精品国产综合久久福利不卡 | 久久久久久久久久久高潮一区二区 | 国产一二区免费视频 | 九九热.com | 亚洲区精品视频 | 精品99久久久久久 | 中文亚洲欧美日韩 | 久久激情视频免费观看 | 久草热视频 | 天天射天 | 色妞久久福利网 | 99r在线 | 国产精品美女久久久 | 中文字幕一区二区三区在线观看 | 久久精品这里都是精品 | 日日日视频 | 国产区欧美 | 国产日韩中文在线 | 在线观看黄a | 中国黄色一级大片 | 国语精品久久 | 999一区二区三区 | 欧美精品久久久 | 久久一区二区三区日韩 | 国产精品国产三级国产aⅴ9色 | 亚洲人av免费网站 | 天天天操天天天干 | 亚洲春色成人 | 国产玖玖在线 | www黄色 | 国产高清绿奴videos | 天堂av影院 | 亚洲婷婷网 | 麻豆传媒视频在线免费观看 | 精品主播网红福利资源观看 | 99久久精品国产亚洲 | 国产精品扒开做爽爽的视频 | 黄色91免费观看 | 亚洲黄色高清 | 麻豆一区二区 | 国产在线污 | 中文字幕在线观看视频一区二区三区 | 在线精品视频在线观看高清 | 日韩高清免费在线 | 综合网av | 日韩三级在线 | 久久手机在线视频 | 欧美91精品国产自产 | av黄色免费网站 | 青青河边草免费观看 | 亚洲欧美日韩一级 | 久久国产精品99久久久久久进口 | 日韩电影中文,亚洲精品乱码 | 亚洲综合狠狠干 | av成人亚洲 | 国产精品亚洲片夜色在线 | 日韩a在线播放 | a黄色| 午夜av免费看 | 国产精品国产亚洲精品看不卡15 | 亚洲精品自拍视频在线观看 | 国产一级在线播放 | 超碰av免费 | 国产精品免费久久久久 | 久久国产亚洲精品 | 99免费精品 | 久久久精品福利视频 | 欧美成人性战久久 | 有码视频在线观看 | 97国产人人 | 国产精品久久久久久久免费大片 | 成人动图| 天天射天天射天天 | 国产精品精品国产 | 综合网av| 日韩大片在线播放 | 日韩色综合网 | 天天操天天艹 | 国产精品午夜久久久久久99热 | 亚洲一区久久久 | 在线观看涩涩 | 成人欧美一区二区三区在线观看 | 久久综合射 | 91精品国产综合久久婷婷香蕉 | 国产激情电影综合在线看 | 久久久久亚洲精品成人网小说 | 一区二区视频电影在线观看 | 特级西西人体444是什么意思 | 夜夜干天天操 | 免费观看一级视频 | 国产精品久久久久久久久久久免费 | 在线香蕉视频 | 一级黄色片在线免费观看 | 国产亚洲精品久久19p | 国产对白av| 亚洲91精品在线观看 | 国产一级淫片在线观看 | 成人一级电影在线观看 | 亚洲一区不卡视频 | 国产精品99久久久久久久久久久久 | 午夜18视频在线观看 | 久久久久久国产精品999 | 在线亚洲人成电影网站色www | 91视视频在线直接观看在线看网页在线看 | 久草在线官网 | 亚洲精品高清视频在线观看 | 国产精品福利小视频 | 久草在线高清 | 亚洲精品影视 | 成人99免费视频 | 亚洲激情综合网 | 五月婷婷综合在线视频 | 99精品在线免费视频 | 日黄网站| 97超碰福利久久精品 | 精品1区2区3区 | 久久久免费观看完整版 | 婷色| 999国产在线 | av丝袜美腿 | 99视频+国产日韩欧美 | 亚洲精品乱码久久久久久9色 | 成年人在线免费看 | 国产分类视频 | 午夜美女av | 国产在线不卡精品 | 不卡的av片| 久久综合网色—综合色88 | 黄污视频网站 | 成年人精品 | 精品日韩在线 | 日本精品xxxx | 亚洲精品国精品久久99热 | 国产一区福利在线 | 国产乱码精品一区二区三区介绍 | 国产特级毛片aaaaaa高清 | 中文字幕欧美三区 | av资源网在线播放 | 成人一级在线观看 | 成人午夜剧场在线观看 | 国产91学生粉嫩喷水 | 成人日韩av| 中文视频在线播放 | 色丁香色婷婷 | 91精品久久久久久 | 久草久草在线观看 | 天天视频色版 | 成人h动漫精品一区二 | 中文字幕 婷婷 | 国产高清精品在线 | 欧美日韩精品综合 | 天天拍天天爽 | 婷婷在线精品视频 | 蜜臀av性久久久久蜜臀aⅴ流畅 | 亚洲国产资源 | 香蕉手机在线 | 久久高清毛片 | 免费看亚洲毛片 | 国产99久久久国产精品 | 伊人干综合 | 国产精品久久精品国产 | 国产精品成人在线观看 | 久久精国产 | 亚洲国产成人在线观看 | 国产视频 亚洲视频 | 成人一级影视 | 国产精品九色 | 国产精品久久久久久久久蜜臀 | 91激情| 国产精品免费一区二区 | 在线视频区 | 国产一区二区在线免费播放 | 亚洲精品大全 | 97精品国自产拍在线观看 | 国产福利91精品一区 | 国精产品999国精产品岳 | 国产亚洲成av片在线观看 | 最近中文字幕国语免费av | av短片在线 | 婷婷激情在线 | 手机看片福利 | 久久综合精品国产一区二区三区 | 涩av在线| 中文字幕在线看视频 | 亚洲国产精品久久久久 | 人人干,人人爽 | 精品国产_亚洲人成在线 | 在线观看一级片 | 97视频免费观看2区 亚洲视屏 | 欧美色噜噜 | 日本久久综合视频 | 91精品国产自产91精品 | 日韩精品短视频 | 草樱av| 久久精品亚洲精品国产欧美 | 久久久视屏 | 中文字幕在线观看视频免费 | a天堂在线看 | 亚洲美女视频网 | 亚洲高清在线精品 | 国产涩图| 欧美一二三区在线播放 | 国产精品久久网 | 免费福利小视频 | 18做爰免费视频网站 | 又黄又爽免费视频 | 黄色1级大片 | 久久免费精彩视频 | 人人干人人艹 | 日韩a级免费视频 | 国产美女久久久 | 亚洲污视频 | 久久只有精品 | 99久久精品久久久久久清纯 | 欧美另类视频 | 岛国av在线 | 久久国产精品影片 | 中文字幕.av.在线 | 精品国内自产拍在线观看视频 | 色橹橹欧美在线观看视频高清 | 99视频国产精品 | 婷婷成人综合 | 成人av在线直播 | 日韩精品观看 | 日韩高清无线码2023 | 久久成电影 | 久久黄色小说 | 国产精品亚州 | 成人资源站| 中文字幕一区二区三区在线播放 | 热久精品| 亚洲精品1区2区3区 超碰成人网 | 日韩欧美精品一区 | 婷婷5月色 | 国精产品一二三线999 | 国产不卡在线播放 | 日韩视频欧美视频 | 欧美在线观看禁18 | 中文字幕在线看视频国产中文版 | 成人黄色片免费看 | 国产免费一区二区三区最新 | 欧美精品一区二区三区一线天视频 | 日韩毛片精品 | 免费在线观看中文字幕 | 国产免费xvideos视频入口 | 69久久夜色精品国产69 | 成人影视免费看 | 免费国产在线精品 | 天天狠狠干 | 天天天天综合 | 91看片淫黄大片一级在线观看 | 就要干b| 国产日产亚洲精华av | 日韩黄色中文字幕 | 在线免费视频一区 | 日韩美在线观看 | 日本黄色免费电影网站 | 在线免费观看国产 | 亚洲精品乱码久久久久久蜜桃欧美 | 麻豆精品国产传媒 | 亚洲成人软件 | 免费看成人片 | 亚洲久草在线视频 | 久久久久高清毛片一级 | 五月天色婷婷丁香 | av一级片网站 | 亚洲天堂va | 亚州精品成人 | 国产成人一区二区精品非洲 | 国精产品永久999 | 日韩国产精品久久 | 亚洲午夜在线视频 | www.天天操 | 福利一区二区在线 | 最新午夜电影 | 欧美国产日韩一区二区 | 国产 欧美 日产久久 | 亚洲特级片 | 在线精品视频免费观看 | 欧美日韩一区二区在线观看 | 欧美日韩一区二区在线观看 | 国产精品久久久网站 | 91亚洲精品乱码久久久久久蜜桃 | 国产精品99久久久精品免费观看 | 国产看片 色 | 激情图片久久 | 国产日产精品一区二区三区四区的观看方式 | 黄色国产大片 | 国产在线视频一区 | 最近更新中文字幕 | 在线国产不卡 | 天天干天天怕 | 欧美日韩视频观看 | 免费在线成人av电影 | 国产91精品久久久久 | av网站免费看| 丁香婷婷激情 | a色视频 | 狠狠干婷婷 | 亚洲国产经典视频 | 婷婷五月情 | 国产成人一区二 | 国产精品中文字幕av | 久久久噜噜噜久久久 | 欧美日韩在线网站 | 五月开心六月伊人色婷婷 | 91精品国产亚洲 | 日日夜夜天天久久 | 免费高清在线观看成人 | 992tv又爽又黄的免费视频 | 黄色av高清 | 免费在线一区二区 | 亚洲精品自拍视频在线观看 | 国产黄色高清 | 免费看国产精品 | 婷婷色社区 | 玖玖玖精品 | 国内精品久久久久影院优 | 日韩精品一区二区在线视频 | 国产精品一区久久久久 | 久久久91精品国产一区二区三区 | 日韩aa视频 | 夜夜操综合网 | 97人人澡人人爽人人模亚洲 | 日日夜夜天天综合 | 亚洲最新在线视频 | 超碰免费在线公开 | 亚洲视频播放 | 日本亚洲国产 | 亚洲三级毛片 | 日韩精品一区二 | 成人黄色电影免费观看 | 在线蜜桃视频 | 国产一级做a爱片久久毛片a | 久久久www免费电影网 | 超碰在线人人97 | 国产精品久久久久久吹潮天美传媒 | 中文字幕乱码视频 | 午夜av激情 | 五月婷婷视频在线 | 96av在线视频| 国产精品av免费在线观看 | 99在线播放| 狠狠操天天射 | 国产精品18久久久久vr手机版特色 | 人人玩人人添人人澡97 | 日韩免费在线观看视频 | 91视频免费看网站 | 狠狠综合久久av | 激情偷乱人伦小说视频在线观看 | 天天色天天射天天操 | 爱射综合 | 久久久久女人精品毛片 | 色婷婷久久一区二区 | 欧美日本不卡视频 | 欧美婷婷综合 | 久久综合狠狠综合久久狠狠色综合 | 欧美日韩高清 | 国产亚洲精品bv在线观看 | 一区二区三区在线观看中文字幕 | 波多野结衣在线视频一区 | 成人午夜影视 | 美女黄濒 | 亚洲精品国偷自产在线91正片 | 欧美最爽乱淫视频播放 | 草久中文字幕 | 亚洲精品国产麻豆 | av东方在线| 99久久精品免费 | 人人看人人| 午夜精品久久 | 福利视频一区二区 | 日韩精品免费一区二区 | 国产一区在线视频播放 | 最新真实国产在线视频 | 天天摸日日摸人人看 | 免费看的av片 | 欧美一二三区在线观看 | 久久99网站 | 国产成人中文字幕 | 亚洲精品国偷拍自产在线观看 | 国产五月 | 国产黑丝一区二区 | 在线免费黄色片 | 亚洲精品一区二区三区新线路 | 日韩一级黄色av | 久久综合久久综合这里只有精品 | 亚洲成人黄色在线观看 | 日韩在线观看a | 国产精品18久久久久白浆 | av观看免费在线 | 特级西西www44高清大胆图片 | 日韩肉感妇bbwbbwbbw | 九色在线视频 | 欧美一级特黄aaaaaa大片在线观看 | 精品久久久久一区二区国产 | 日韩在线免费不卡 | 久久无码精品一区二区三区 | 很污的网站 | 在线播放第一页 | 亚洲春色综合另类校园电影 | 在线观看视频91 | 在线看不卡av | 国产视频资源在线观看 | 美女视频黄,久久 | 国产精品电影在线 | 日韩激情精品 | 国产亚洲精品久久久久久无几年桃 | 国产精品二区在线观看 | 成人免费av电影 | 婷婷激情综合网 | 免费男女羞羞的视频网站中文字幕 | 久久国产网站 | 欧洲在线免费视频 | 日韩免费视频在线观看 | 欧美一级淫片videoshd | 三上悠亚一区二区在线观看 | 亚洲精品456在线播放 | 日日操日日操 | 国产精品9999久久久久仙踪林 | 亚洲免费国产 | 欧美在线视频a | 天天看天天操 | 久久免费看毛片 | 美女中文字幕 | 国产中文字幕在线播放 | 少妇性xxx | 国产精品永久久久久久久久久 | 国产色女人 | 日韩在线视频线视频免费网站 | 久草在线免费看视频 | 久草在线视频新 | 97视频在线免费观看 | 97电影院在线观看 | 久久99精品久久久久久秒播蜜臀 | 99久久99久久免费精品蜜臀 | 色综合久久久久综合体桃花网 | 特级西西444www大精品视频免费看 | 欧美激情操 | 在线观看av网 | 99久久99久久精品免费 | 狠狠狠狠干 | 欧美日韩性生活 | 国产青草视频在线观看 | 国产一区欧美日韩 | 嫩草av影院 | 毛片一区二区 | 91精品一区二区三区蜜桃 | 亚洲精品99久久久久中文字幕 | 免费黄色网址网站 | 久草在线视频在线 | 国产精品免费一区二区 | 国产成人精品久久二区二区 | 99热国产在线 | 2018精品视频 | 国产精品久久久久久久久久久久午夜 | 狠狠色狠狠色综合日日92 | 久久一区91| 久久天天躁夜夜躁狠狠85麻豆 | 成人av在线网址 | 超碰免费公开 | 国内精品久久久久久久久久清纯 | 欧美日韩一区二区三区不卡 | 四虎影视成人永久免费观看亚洲欧美 | 国产不卡免费视频 | 午夜精品久久久久久中宇69 | 韩国av免费观看 | 国产成人亚洲精品自产在线 | 国产精品h在线观看 | 黄色.com | 在线免费看黄网站 | 亚洲高清视频在线播放 | 91精品999 | 国产成人精品久久二区二区 | 国产精品久久av | 不卡中文字幕av | 欧美日韩国产在线 | 国产精品久久久久久超碰 | 欧美性大战久久久久 | 成人蜜桃网| 色播99| 免费亚洲精品视频 | 69精品视频在线观看 | 成人h电影在线观看 | 日韩性久久 | 久草视频中文 | 国产又粗又猛又黄又爽的视频 | 国产精品99久久久久久有的能看 | 这里只有精品视频在线观看 | 中文在线a天堂 | av网站有哪些 | 69久久久 | 国产日本亚洲 | 91精品成人 | 久久不射网站 | 香蕉久草在线 | 久草在线资源网 | 免费成人在线观看视频 | 日韩在观看线 | 久日精品 | 国产成人精品999 | 中文在线最新版天堂 | 国产91精品一区二区麻豆亚洲 | 国产成人精品在线观看 | 久草久草久草久草 | 久久久久综合 | 久久综合狠狠综合久久激情 | 中文在线字幕观看电影 | 日本黄色大片儿 | 国产精品成人免费一区久久羞羞 | 久久精品国产一区 | 99热官网 | 亚洲精品资源在线观看 | 精品国产一区二区三区在线观看 | 韩国av免费在线观看 | www.com黄| 四虎免费av | 欧美一级电影在线观看 | 久久精品99国产精品亚洲最刺激 | 成人av影视在线 | 亚洲精品免费看 | av一级在线观看 | 99精品视频免费在线观看 | 免费久久99精品国产婷婷六月 | 成人精品国产免费网站 | 免费在线中文字幕 | 日韩久久精品一区二区三区 | 成年人免费av网站 | 视频国产在线 | 久久久免费在线观看 | www.av在线.com | 国产极品尤物在线 | 久久久九色精品国产一区二区三区 | 亚洲影视九九影院在线观看 | 97av视频在线 | 久久久精品国产一区二区三区 | 色婷婷88av视频一二三区 | 91 在线视频播放 | 精品久久久久久久久久久久久久久久久久 | 在线观看免费高清视频大全追剧 | 九九热av| 在线视频 影院 | av在线色| 国产欧美中文字幕 | 久久国产剧场电影 | 久久免费视频网 | 久久一区二区免费视频 | 久久精品人人做人人综合老师 | 免费精品国产va自在自线 | 国产精品视频内 | 在线 视频 一区二区 | 在线日韩精品视频 | 国产日韩欧美综合在线 | 麻豆91在线观看 | 91精品在线免费 | 五月婷婷天堂 | 人人天天夜夜 | 久热av在线 | 青春草免费视频 | 99爱精品视频 | 天天色天天色 | 91最新地址永久入口 | 91精品黄色 | 久久久视频在线 | 久久综合视频网 | 亚洲最新av| 国产成人精品福利 | 亚洲国产视频在线 | 久久在现 | 麻豆视频成人 | 一区二区三区四区五区六区 | 激情综合中文娱乐网 | 日本丰满少妇免费一区 | 伊人影院av| 国产精品久久久久久久久久99 | 国产成人久久精品亚洲 | 亚洲国产精品久久久久久 | 国产亚洲精品成人av久久影院 | 91在线porny国产在线看 | 国产精品一区二区三区在线 | 四虎国产永久在线精品 | 国产专区视频 | 国产午夜精品一区二区三区在线观看 | 国产精品福利久久久 | 在线99视频 | 超碰97人 | av色图天堂网 | 国产日产av| 五月天狠狠操 | 欧美乱熟臀69xxxxxx | 18av在线视频 | 丁香网婷婷 | 久久精品99国产精品酒店日本 | 久色 网 | 亚洲一级二级三级 | 在线视频中文字幕一区 | 久久99精品久久久久久 | 69av视频在线观看 | 国产aa精品 | 国产日韩中文字幕 | 你操综合 | 久久免费在线观看 | 91一区二区三区在线观看 | 欧美热久久 | 91精品视频播放 | 九色精品免费永久在线 | 国产福利一区二区三区视频 | 99视频精品免费观看, | 成人福利在线观看 | 一区二区不卡视频在线观看 | 久久热首页 | 91成年视频 | 涩涩网站免费 | 日韩三级精品 | 少妇搡bbbb搡bbb搡忠贞 | 最近中文字幕高清字幕免费mv | 免费网站黄| 亚洲国产精品成人综合 | 亚洲伊人成综合网 | 亚在线播放中文视频 | 亚洲欧美日韩一级 | 一区三区视频 | 日韩精品一区二区三区视频播放 | 中文av网| 九九九九免费视频 | 欧美性高跟鞋xxxxhd | 五月天欧美精品 | 国产激情小视频在线观看 | 97色se| 啪啪肉肉污av国网站 | 久久久亚洲国产精品麻豆综合天堂 | 九九视频免费在线观看 | 成人国产精品免费观看 | 久久伦理 | 国产精品久久久久久一区二区 | 狠狠色噜噜狠狠 | 国产视频综合在线 | 香蕉视频啪啪 | 久久免费视频8 | 日韩 在线观看 | 天天爽天天搞 | 久久精品黄| 欧美91av| 五月婷在线播放 | av色一区| 在线观看中文字幕视频 | 九九视频在线 | 日本韩国精品在线 | 免费观看国产精品 | 国产精品成人久久 | 亚洲精品欧美专区 | 久久久久国产精品厨房 | 久久久免费电影 | 丁香资源影视免费观看 | 国产精品免费看久久久8精臀av | 五月婷婷丁香 | 国产成人精品免高潮在线观看 | 午夜久久久影院 | 亚洲aⅴ在线观看 | 91黄色在线看| 久久精品免费播放 |