黄a在线观看-黄a在线-黄a大片-黄色片在线看-黄色毛片免费-黄色大片网站

您的位置:首頁技術文章
文章詳情頁

基于Spring Security前后端分離的權限控制系統問題

瀏覽:18日期:2023-07-08 18:48:45
目錄1. 引入maven依賴2. 建表并生成相應的實體類3. 自定義UserDetails4. 自定義各種Handler5. Token處理6. 訪問控制7. 配置WebSecurity8. 看效果9. 補充:手機號+短信驗證碼登錄

前后端分離的項目,前端有菜單(menu),后端有API(backendApi),一個menu對應的頁面有N個API接口來支持,本文介紹如何基于Spring Security前后端分離的權限控制系統問題。

話不多說,入正題。一個簡單的權限控制系統需要考慮的問題如下:

權限如何加載 權限匹配規則 登錄1. 引入maven依賴

<?xml version='1.0' encoding='UTF-8'?> <project xmlns='http://maven.apache.org/POM/4.0.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd'> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.1</version><relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo5</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo5</name> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <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-web</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>

application.properties配置

server.port=8080 server.servlet.context-path=/demo spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=123456 spring.jpa.database=mysql spring.jpa.open-in-view=true spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true spring.jpa.show-sql=true spring.redis.host=192.168.28.31 spring.redis.port=6379 spring.redis.password=1234562. 建表并生成相應的實體類

基于Spring Security前后端分離的權限控制系統問題

SysUser.java

package com.example.demo5.entity; import lombok.Getter; import lombok.Setter; import javax.persistence.*; import java.io.Serializable; import java.time.LocalDate; import java.util.Set; /** * 用戶表 * @Author ChengJianSheng * @Date 2021/6/12 */ @Setter @Getter @Entity @Table(name = 'sys_user') public class SysUserEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = 'id') private Integer id; @Column(name = 'username') private String username; @Column(name = 'password') private String password; @Column(name = 'mobile') private String mobile; @Column(name = 'enabled') private Integer enabled; @Column(name = 'create_time') private LocalDate createTime; @Column(name = 'update_time') private LocalDate updateTime; @OneToOne @JoinColumn(name = 'dept_id') private SysDeptEntity dept; @ManyToMany @JoinTable(name = 'sys_user_role', joinColumns = {@JoinColumn(name = 'user_id', referencedColumnName = 'id')}, inverseJoinColumns = {@JoinColumn(name = 'role_id', referencedColumnName = 'id')}) private Set<SysRoleEntity> roles; }

SysDept.java

部門相當于用戶組,這里簡化了一下,用戶組沒有跟角色管理

package com.example.demo5.entity; import lombok.Data; import javax.persistence.*; import java.io.Serializable; import java.util.Set; /** * 部門表 * @Author ChengJianSheng * @Date 2021/6/12 */ @Data @Entity @Table(name = 'sys_dept') public class SysDeptEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = 'id') private Integer id; /** * 部門名稱 */ @Column(name = 'name') private String name; /** * 父級部門ID */ @Column(name = 'pid') private Integer pid; // @ManyToMany(mappedBy = 'depts') // private Set<SysRoleEntity> roles; }

SysMenu.java

菜單相當于權限

package com.example.demo5.entity; import lombok.Data; import lombok.Getter; import lombok.Setter; import javax.persistence.*; import java.io.Serializable; import java.util.Set; /** * 菜單表 * @Author ChengJianSheng * @Date 2021/6/12 */ @Setter @Getter @Entity @Table(name = 'sys_menu') public class SysMenuEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = 'id') private Integer id; /** * 資源編碼 */ @Column(name = 'code') private String code; /** * 資源名稱 */ @Column(name = 'name') private String name; /** * 菜單/按鈕URL */ @Column(name = 'url') private String url; /** * 資源類型(1:菜單,2:按鈕) */ @Column(name = 'type') private Integer type; /** * 父級菜單ID */ @Column(name = 'pid') private Integer pid; /** * 排序號 */ @Column(name = 'sort') private Integer sort; @ManyToMany(mappedBy = 'menus') private Set<SysRoleEntity> roles; }

SysRole.java

package com.example.demo5.entity; import lombok.Data; import lombok.Getter; import lombok.Setter; import javax.persistence.*; import java.io.Serializable; import java.util.Set; /** * 角色表 * @Author ChengJianSheng * @Date 2021/6/12 */ @Setter @Getter @Entity @Table(name = 'sys_role') public class SysRoleEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = 'id') private Integer id; /** * 角色名稱 */ @Column(name = 'name') private String name; @ManyToMany(mappedBy = 'roles') private Set<SysUserEntity> users; @ManyToMany @JoinTable(name = 'sys_role_menu', joinColumns = {@JoinColumn(name = 'role_id', referencedColumnName = 'id')}, inverseJoinColumns = {@JoinColumn(name = 'menu_id', referencedColumnName = 'id')}) private Set<SysMenuEntity> menus; // @ManyToMany // @JoinTable(name = 'sys_dept_role', // joinColumns = {@JoinColumn(name = 'role_id', referencedColumnName = 'id')}, // inverseJoinColumns = {@JoinColumn(name = 'dept_id', referencedColumnName = 'id')}) // private Set<SysDeptEntity> depts; }

注意,不要使用@Data注解,因為@Data包含@ToString注解

不要隨便打印SysUser,例如:System.out.println(sysUser); 任何形式的toString()調用都不要有,否則很有可能造成循環調用,死遞歸。想想看,SysUser里面要查SysRole,SysRole要查SysMenu,SysMenu又要查SysRole。除非不用懶加載。

基于Spring Security前后端分離的權限控制系統問題

3. 自定義UserDetails

雖然可以使用Spring Security自帶的User,但是筆者還是強烈建議自定義一個UserDetails,后面可以直接將其序列化成json緩存到redis中

package com.example.demo5.domain; import lombok.Setter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.Set; /** * @Author ChengJianSheng * @Date 2021/6/12 * @see User * @see org.springframework.security.core.userdetails.User */ @Setter public class MyUserDetails implements UserDetails { private String username; private String password; private boolean enabled; // private Collection<? extends GrantedAuthority> authorities; private Set<SimpleGrantedAuthority> authorities; public MyUserDetails(String username, String password, boolean enabled, Set<SimpleGrantedAuthority> authorities) { this.username = username; this.password = password; this.enabled = enabled; this.authorities = authorities; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enabled; } }

都自定義UserDetails了,當然要自己實現UserDetailsService了。這里當時偷懶直接用自帶的User,后面放緩存的時候才知道不方便。

package com.example.demo5.service; import com.example.demo5.entity.SysMenuEntity; import com.example.demo5.entity.SysRoleEntity; import com.example.demo5.entity.SysUserEntity; import com.example.demo5.repository.SysUserRepository; import org.apache.commons.lang3.StringUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; 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 javax.annotation.Resource; import java.util.Set; import java.util.stream.Collectors; /** * @Author ChengJianSheng * @Date 2021/6/12 */ @Service public class MyUserDetailsService implements UserDetailsService { @Resource private SysUserRepository sysUserRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUserEntity sysUserEntity = sysUserRepository.findByUsername(username); Set<SysRoleEntity> roleSet = sysUserEntity.getRoles(); Set<SimpleGrantedAuthority> authorities = roleSet.stream().flatMap(role->role.getMenus().stream()) .filter(menu-> StringUtils.isNotBlank(menu.getCode())) .map(SysMenuEntity::getCode) .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); User user = new User(sysUserEntity.getUsername(), sysUserEntity.getPassword(), authorities); return user; } }

算了,還是改過來吧

package com.example.demo5.service; import com.example.demo5.domain.MyUserDetails; import com.example.demo5.entity.SysMenuEntity; import com.example.demo5.entity.SysRoleEntity; import com.example.demo5.entity.SysUserEntity; import com.example.demo5.repository.SysUserRepository; import org.apache.commons.lang3.StringUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; 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 javax.annotation.Resource; import java.util.Set; import java.util.stream.Collectors; /** * @Author ChengJianSheng * @Date 2021/6/12 */ @Service public class MyUserDetailsService implements UserDetailsService { @Resource private SysUserRepository sysUserRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUserEntity sysUserEntity = sysUserRepository.findByUsername(username); Set<SysRoleEntity> roleSet = sysUserEntity.getRoles(); Set<SimpleGrantedAuthority> authorities = roleSet.stream().flatMap(role->role.getMenus().stream()) .filter(menu-> StringUtils.isNotBlank(menu.getCode())) .map(SysMenuEntity::getCode) .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); //return new User(sysUserEntity.getUsername(), sysUserEntity.getPassword(), authorities); return new MyUserDetails(sysUserEntity.getUsername(), sysUserEntity.getPassword(), 1==sysUserEntity.getEnabled(), authorities); } }4. 自定義各種Handler

登錄成功

package com.example.demo5.handler; import com.alibaba.fastjson.JSON; import com.example.demo5.domain.MyUserDetails; import com.example.demo5.domain.RespResult; import com.example.demo5.util.JwtUtils; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.concurrent.TimeUnit; /** * 登錄成功 */ @Component public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private static ObjectMapper objectMapper = new ObjectMapper(); @Autowired private StringRedisTemplate stringRedisTemplate; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { MyUserDetails user = (MyUserDetails) authentication.getPrincipal(); String username = user.getUsername(); String token = JwtUtils.createToken(username); stringRedisTemplate.opsForValue().set('TOKEN:' + token, JSON.toJSONString(user), 60, TimeUnit.MINUTES); response.setContentType('application/json;charset=utf-8'); PrintWriter writer = response.getWriter(); writer.write(objectMapper.writeValueAsString(new RespResult<>(1, 'success', token))); writer.flush(); writer.close(); } }

登錄失敗

package com.example.demo5.handler; import com.example.demo5.domain.RespResult; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 登錄失敗 */ @Component public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { private static ObjectMapper objectMapper = new ObjectMapper(); @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setContentType('application/json;charset=utf-8'); PrintWriter writer = response.getWriter(); writer.write(objectMapper.writeValueAsString(new RespResult<>(0, exception.getMessage(), null))); writer.flush(); writer.close(); } }

未登錄

package com.example.demo5.handler; import com.example.demo5.domain.RespResult; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 未認證(未登錄)統一處理 * @Author ChengJianSheng * @Date 2021/5/7 */ @Component public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint { private static ObjectMapper objectMapper = new ObjectMapper(); @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setContentType('application/json;charset=utf-8'); PrintWriter writer = response.getWriter(); writer.write(objectMapper.writeValueAsString(new RespResult<>(0, '未登錄,請先登錄', null))); writer.flush(); writer.close(); } }

未授權

package com.example.demo5.handler; import com.example.demo5.domain.RespResult; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @Component public class MyAccessDeniedHandler implements AccessDeniedHandler { private static ObjectMapper objectMapper = new ObjectMapper(); @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setContentType('application/json;charset=utf-8'); PrintWriter writer = response.getWriter(); writer.write(objectMapper.writeValueAsString(new RespResult<>(0, '抱歉,您沒有權限訪問', null))); writer.flush(); writer.close(); } }

Session過期

package com.example.demo5.handler; import com.example.demo5.domain.RespResult; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.web.session.SessionInformationExpiredEvent; import org.springframework.security.web.session.SessionInformationExpiredStrategy; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class MyExpiredSessionStrategy implements SessionInformationExpiredStrategy { private static ObjectMapper objectMapper = new ObjectMapper(); @Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException { String msg = '登錄超時或已在另一臺機器登錄,您被迫下線!'; RespResult respResult = new RespResult(0, msg, null); HttpServletResponse response = event.getResponse(); response.setContentType('application/json;charset=utf-8'); PrintWriter writer = response.getWriter(); writer.write(objectMapper.writeValueAsString(respResult)); writer.flush(); writer.close(); } }

退出成功

package com.example.demo5.handler; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @Component public class MyLogoutSuccessHandler implements LogoutSuccessHandler { private static ObjectMapper objectMapper = new ObjectMapper(); @Autowired private StringRedisTemplate stringRedisTemplate; @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String token = request.getHeader('token'); stringRedisTemplate.delete('TOKEN:' + token); response.setContentType('application/json;charset=utf-8'); PrintWriter printWriter = response.getWriter(); printWriter.write(objectMapper.writeValueAsString('logout success')); printWriter.flush(); printWriter.close(); } }5. Token處理

現在由于前后端分離,服務端不再維持Session,于是需要token來作為訪問憑證

token工具類

package com.example.demo5.util; import io.jsonwebtoken.*; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; /** * @Author ChengJianSheng * @Date 2021/5/7 */ public class JwtUtils { private static long TOKEN_EXPIRATION = 24 * 60 * 60 * 1000; private static String TOKEN_SECRET_KEY = '123456'; /** * 生成Token * @param subject 用戶名 * @return */ public static String createToken(String subject) { long currentTimeMillis = System.currentTimeMillis(); Date currentDate = new Date(currentTimeMillis); Date expirationDate = new Date(currentTimeMillis + TOKEN_EXPIRATION); // 存放自定義屬性,比如用戶擁有的權限 Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(currentDate) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, TOKEN_SECRET_KEY) .compact(); } public static String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } public static boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } public static Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } public static <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } private static Claims extractAllClaims(String token) { return Jwts.parser().setSigningKey(TOKEN_SECRET_KEY).parseClaimsJws(token).getBody(); } }

前后端約定登錄成功以后,將token放到header中。于是,我們需要過濾器來處理請求Header中的token,為此定義一個TokenFilter

package com.example.demo5.filter; import com.alibaba.fastjson.JSON; import com.example.demo5.domain.MyUserDetails; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.concurrent.TimeUnit; /** * @Author ChengJianSheng * @Date 2021/6/17 */ @Component public class TokenFilter extends OncePerRequestFilter { @Autowired private StringRedisTemplate stringRedisTemplate; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token = request.getHeader('token'); System.out.println('請求頭中帶的token: ' + token); String key = 'TOKEN:' + token; if (StringUtils.isNotBlank(token)) { String value = stringRedisTemplate.opsForValue().get(key); if (StringUtils.isNotBlank(value)) { //String username = JwtUtils.extractUsername(token); MyUserDetails user = JSON.parseObject(value, MyUserDetails.class); if (null != user && null == SecurityContextHolder.getContext().getAuthentication()) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); // 刷新token // 如果生存時間小于10分鐘,則再續1小時 long time = stringRedisTemplate.getExpire(key); if (time < 600) { stringRedisTemplate.expire(key, (time + 3600), TimeUnit.SECONDS); } } } } chain.doFilter(request, response); } }

token過濾器做了兩件事,一是獲取header中的token,構造UsernamePasswordAuthenticationToken放入上下文中。權限可以從數據庫中再查一遍,也可以直接從之前的緩存中獲取。二是為token續期,即刷新token。

由于我們采用jwt生成token,因此沒法中途更改token的有效期,只能將其放到Redis中,通過更改Redis中key的生存時間來控制token的有效期。

6. 訪問控制

首先來定義資源

package com.example.demo5.controller; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author ChengJianSheng * @Date 2021/6/12 */ @RestController @RequestMapping('/hello') public class HelloController { @PreAuthorize('@myAccessDecisionService.hasPermission(’hello:sayHello’)') @GetMapping('/sayHello') public String sayHello() { return 'hello'; } @PreAuthorize('@myAccessDecisionService.hasPermission(’hello:sayHi’)') @GetMapping('/sayHi') public String sayHi() { return 'hi'; } }

資源的訪問控制我們通過判斷是否有相應的權限字符串

package com.example.demo5.service; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.Set; import java.util.stream.Collectors; @Component('myAccessDecisionService') public class MyAccessDecisionService { public boolean hasPermission(String permission) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails) { UserDetails userDetails = (UserDetails) principal; // SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission); Set<String> set = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); return set.contains(permission); } return false; } }7. 配置WebSecurity

package com.example.demo5.config; import com.example.demo5.filter.TokenFilter; import com.example.demo5.handler.*; import com.example.demo5.service.MyUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * @Author ChengJianSheng * @Date 2021/6/12 */ @EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService myUserDetailsService; @Autowired private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; @Autowired private MyAuthenticationFailureHandler myAuthenticationFailureHandler; @Autowired private TokenFilter tokenFilter; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() //.usernameParameter('username') //.passwordParameter('password') //.loginPage('/login.html') .successHandler(myAuthenticationSuccessHandler) .failureHandler(myAuthenticationFailureHandler) .and() .logout().logoutSuccessHandler(new MyLogoutSuccessHandler()) .and() .authorizeRequests() .antMatchers('/demo/login').permitAll() //.antMatchers('/css/**', '/js/**', '/**/images/*.*').permitAll() //.regexMatchers('.+[.]jpg').permitAll() //.mvcMatchers('/hello').servletPath('/demo').permitAll() .anyRequest().authenticated() .and() .exceptionHandling() .accessDeniedHandler(new MyAccessDeniedHandler()) .authenticationEntryPoint(new MyAuthenticationEntryPoint()) .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .maximumSessions(1) .maxSessionsPreventsLogin(false) .expiredSessionStrategy(new MyExpiredSessionStrategy()); http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class); http.csrf().disable(); } public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } public static void main(String[] args) { System.out.println(new BCryptPasswordEncoder().encode('123456')); } }

注意,我們將自定義的TokenFilter放到UsernamePasswordAuthenticationFilter之前

所有過濾器的順序可以查看 org.springframework.security.config.annotation.web.builders.FilterComparator 或者org.springframework.security.config.annotation.web.builders.FilterOrderRegistration

8. 看效果

基于Spring Security前后端分離的權限控制系統問題

基于Spring Security前后端分離的權限控制系統問題

基于Spring Security前后端分離的權限控制系統問題

基于Spring Security前后端分離的權限控制系統問題

基于Spring Security前后端分離的權限控制系統問題

基于Spring Security前后端分離的權限控制系統問題

9. 補充:手機號+短信驗證碼登錄

參照org.springframework.security.authentication.UsernamePasswordAuthenticationToken寫一個短信認證Token

package com.example.demo5.filter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.util.Assert; import java.util.Collection; /** * @Author ChengJianSheng * @Date 2021/5/12 */ public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; private final Object principal; private Object credentials; public SmsCodeAuthenticationToken(Object principal, Object credentials) { super(null); this.principal = principal; this.credentials = credentials; setAuthenticated(false); } public SmsCodeAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); } @Override public Object getCredentials() { return credentials; } @Override public Object getPrincipal() { return principal; } @Override public void setAuthenticated(boolean authenticated) { Assert.isTrue(!authenticated, 'Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead'); super.setAuthenticated(false); } @Override public void eraseCredentials() { super.eraseCredentials(); } }

參照org.springframework.security.authentication.dao.DaoAuthenticationProvider寫一個自己的短信認證Provider

package com.example.demo5.filter; import com.example.demo.service.MyUserDetailsService; import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; /** * @Author ChengJianSheng * @Date 2021/5/12 */ public class SmsAuthenticationProvider implements AuthenticationProvider { private MyUserDetailsService myUserDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 校驗驗證碼 additionalAuthenticationChecks((SmsCodeAuthenticationToken) authentication); // 校驗手機號 String mobile = authentication.getPrincipal().toString(); UserDetails userDetails = myUserDetailsService.loadUserByMobile(mobile); if (null == userDetails) { throw new BadCredentialsException('手機號不存在'); } // 創建認證成功的Authentication對象 SmsCodeAuthenticationToken result = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities()); result.setDetails(authentication.getDetails()); return result; } protected void additionalAuthenticationChecks(SmsCodeAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { throw new BadCredentialsException('驗證碼不能為空'); } String mobile = authentication.getPrincipal().toString(); String smsCode = authentication.getCredentials().toString(); // 從Session或者Redis中獲取相應的驗證碼 String smsCodeInSessionKey = 'SMS_CODE_' + mobile; //String verificationCode = sessionStrategy.getAttribute(servletWebRequest, smsCodeInSessionKey); //String verificationCode = stringRedisTemplate.opsForValue().get(smsCodeInSessionKey); String verificationCode = '1234'; if (StringUtils.isBlank(verificationCode)) { throw new BadCredentialsException('短信驗證碼不存在,請重新發送!'); } if (!smsCode.equalsIgnoreCase(verificationCode)) { throw new BadCredentialsException('驗證碼錯誤!'); } //todo 清除Session或者Redis中獲取相應的驗證碼 } @Override public boolean supports(Class<?> authentication) { return (SmsCodeAuthenticationToken.class.isAssignableFrom(authentication)); } public MyUserDetailsService getMyUserDetailsService() { return myUserDetailsService; } public void setMyUserDetailsService(MyUserDetailsService myUserDetailsService) { this.myUserDetailsService = myUserDetailsService; } }

參照org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter寫一個短信認證處理的過濾器

package com.example.demo.filter; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @Author ChengJianSheng * @Date 2021/5/12 */ public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_MOBILE_KEY = 'mobile'; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = 'smsCode'; private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher('/login/mobile', 'POST'); private String usernameParameter = SPRING_SECURITY_FORM_MOBILE_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; private boolean postOnly = true; public SmsAuthenticationFilter() { super(DEFAULT_ANT_PATH_REQUEST_MATCHER); } public SmsAuthenticationFilter(AuthenticationManager authenticationManager) { super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { if (postOnly && !request.getMethod().equals('POST')) { throw new AuthenticationServiceException('Authentication method not supported: ' + request.getMethod()); } String mobile = obtainMobile(request); mobile = (mobile != null) ? mobile : ''; mobile = mobile.trim(); String smsCode = obtainPassword(request); smsCode = (smsCode != null) ? smsCode : ''; SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile, smsCode); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } private String obtainMobile(HttpServletRequest request) { return request.getParameter(this.usernameParameter); } private String obtainPassword(HttpServletRequest request) { return request.getParameter(this.passwordParameter); } protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) { authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); } }

在WebSecurity中進行配置

package com.example.demo.config; import com.example.demo.filter.SmsAuthenticationFilter; import com.example.demo.filter.SmsAuthenticationProvider; import com.example.demo.handler.MyAuthenticationFailureHandler; import com.example.demo.handler.MyAuthenticationSuccessHandler; import com.example.demo.service.MyUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.stereotype.Component; /** * @Author ChengJianSheng * @Date 2021/5/12 */ @Component public class SmsAuthenticationConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired private MyUserDetailsService myUserDetailsService; @Autowired private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; @Autowired private MyAuthenticationFailureHandler myAuthenticationFailureHandler; @Override public void configure(HttpSecurity http) throws Exception { SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter(); smsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); smsAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler); smsAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler); SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider(); smsAuthenticationProvider.setMyUserDetailsService(myUserDetailsService); http.authenticationProvider(smsAuthenticationProvider) .addFilterAfter(smsAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } } http.apply(smsAuthenticationConfig);

以上就是基于 Spring Security前后端分離的權限控制系統的詳細內容,更多關于Spring Security權限控制系統的資料請關注好吧啦網其它相關文章!

標簽: Spring
相關文章:
主站蜘蛛池模板: av网站在线免费 | 久久综合91 | 777精品久无码人妻蜜桃 | 久草福利资源 | 国产小视频网址 | 国产成人无码av一区二区在线观看 | 麻豆最新网址 | 亚洲日韩看片无码超清 | 国产看真人毛片爱做a片 | 国产在热线精品av | 91中文在线 | 欧美一区二区三区免费播放视频了 | 国产区91| 喷水白丝蜜臀av久久av | 日韩人妻无码精品久久久不卡 | 日韩精品久久久久久免费 | 乱h高h女np群欢 | 国产日韩在线一区 | 国产视频自拍一区 | 搡女人真爽免费午夜网站 | 日韩中文字幕在线免费观看 | 国产精品久久久久国产a级 国产精品久久久久国产三级传媒 | 中文字幕校园春色 | 国产做a爱片久久毛片 | 天天操综合网 | 日本午夜一级 | 中文字幕亚洲综合久久综合 | 欧美精品久久一区二区 | youjizzcom国产| 无码h黄动漫在线播放网站 国产精品高潮露脸在线观看 | 亚洲欧美国产精品久久久久久久 | 在线一区二区三区在线一区 | 天天躁日日躁狠狠躁av麻豆 | 美女视频黄a视频免费全程软件axs | 自拍偷自拍亚洲精品播放 | 日韩成人综合 | 自拍亚洲综合 | 青青草华人在线 | 国产午夜福利精品一区二区三区 | 色播在线播放 | 91精品啪在线观看国产老湿机 | 亚洲国产a∨无码中文777 | 成人激情视频网 | 雨宫琴音av一区在线播放 | 天堂网中文在线 | 韩国精品一区二区三区四区 | 免费国产自产一区二区三区四区 | 日本乱码一区二区三区芒果 | 久久久精品一区二区 | 日日射天天干 | 日本视频中文字幕 | 国产精品美女久久久久久久 | 久精品在线 | 午夜精品久久ed2kmp4 | 麻花传媒在线mv免费观看视频 | 国产精品一区二区av不卡 | 中文字幕乱偷在线小说 | 一区二区三区国产 | 欧美怡红院免费全部视频 | 九一色视频| 强辱丰满人妻hd中文字幕 | 亚洲乱码日产精品bd在线看 | 四虎精品成人免费视频 | 国产一区二区视频播放 | 国产区精品在线 | 成人国内精品久久久久影院成.人国产9 | 国产91精品一区二区三区四区 | 国产在线视欧美亚综合 | 日韩中文字幕免费 | 日本在线一区二区 | 精品久久久久久一区二区里番 | 国产高清久久久 | 亚洲精品av久久久久久久影院 | 色婷婷香蕉 | 欧美中文在线视频 | 91视频成人免费 | 一区二区三区成人 | 大地资源网第二页免费观看 | 精品在线视频一区二区三区 | 热久久中文 | 青草国产精品久久久久久 | 国产中文字幕一区 | 少妇久久久久久被弄高潮 | 日本大尺度吃奶做爰久久久绯色 | 韩国边摸边做呻吟激情 | 一区二区三区小说 | 老湿机69福利区无码 | 99精品国产免费久久久久久按摩 | 日本三级吃奶头添泬无码苍井空 | 欧美性生活网 | 中文字幕亚洲中文字幕无码码 | 中文字幕成人在线观看 | 日韩不卡在线播放 | 免费成年人视频网站 | 成人国产精品免费网站 | 中文字幕一区二区三区四区五区 | 国产精品高潮呻吟视频 | 亚洲国产中文字幕 | 调教女少妇二区三区视频 | 最新午夜综合福利视频 | 欧美激情91 | 国产一卡二卡三卡四卡 | 精品国产区 | 中文字幕日本六区小电影 | 91精品久久久久久久久99蜜臂 | 日本女优在线看 | 波多野结衣视频在线 | 99re6在线视频精品免费 | 99精品国产兔费观看久久99 | 亚洲黄色在线观看视频 | 亚洲五码av| 国产成人亚洲精品自产在线 | 中国女人一级片 | 邻居少妇张开双腿让我爽一夜 | 亚洲一区二区图片 | 亚洲欧美中文字幕 | 国产喷白浆一区二区三区 | 国产色婷婷亚洲99精品小说 | 欧类av怡春院 | 国产精品久久久av久久久 | 日韩欧美操| 女人做爰全过程免费观看美女 | 男女天堂av| 天天躁日日躁狠狠躁免费麻豆 | 天天干,天天操,天天射 | 男女后进式猛烈xx00动态图片 | 亚洲欧美成人aⅴ大片 | 国产欧美中文字幕 | 一本之道色综合网站 | 国产精品日本一区二区在线播放 | 91在线视频免费观看 | 国外处破女一区二区 | av片免费| 日韩中文字幕在线观看 | 香港三级韩国三级日本三级 | 少妇又粗又猛又爽又黄的视频 | 久久九九热视频 | 韩日精品视频在线观看 | 日韩精品乱码av一区二区 | 久久久精品中文字幕麻豆发布 | mm131美女大尺度私密照尤果 | 少妇无套内谢久久久久 | 色视频久久 | 亚洲欧美日韩国产手机在线 | 绯色av蜜臀一区二区中文字幕 | 99精品久久久久久久 | 最近日韩免费视频 | 国产乱xxxxx987国语对白 | 寂寞骚妇被后入式爆草抓爆 | 少妇丰满尤物大尺度写真 | 欧美不卡影院 | 性殴美69xoxoxoxo| 欧美三级一级片 | 天天射中文 | 欧美日韩国产色 | 北条麻妃一区二区三区av | 久久国产乱子伦免费精品 | 91视频在线观看视频 | 无码人妻一区二区三区av | 一本久久伊人热热精品中文字幕 | 高h七仙女辣黄h | youjizz国产精品 | 国产黄色免费大片 | 亚洲日韩av在线观看 | 最近日本免费观看高清视频 | 欧美日韩国产成人高清视频 | 精品国产乱码久久久久久移动网络 | 国产老女人乱淫免费可以 | 99草草国产熟女视频在线 | 日本人妻丰满熟妇久久久久久 | 日韩中文字幕亚洲精品欧美 | 精品无码国产自产拍在线观看蜜 | 一级全黄色毛片 | 亚洲视频在线观看免费视频 | 噜妇插内射精品 | 欧美最猛性xxxxx(亚洲精品) | 一本之道ay免费 | 毛片视频免费观看 | 欧美精品久久99 | av在线片 | 日韩av手机在线播放 | 黄色片在哪看 | 国产女人和拘做受视频免费 | 亚洲一区国产一区 | 亚洲日韩精品一区二区三区 | 91精品国产亚洲 | 美女高清视频免费视频 | 精品综合 | 国产精品久久久久久人妻精品 | 欧美在线激情 | 乱色欧美 | 日本一本高清 | 欧美日韩se | 产乳奶水文h男男喂奶 | 日日摸夜夜添夜夜添欧美毛片小说 | 综合色在线观看 | 爱爱爱爱网站 | 久久亚洲综合网 | 日韩黄色影院 | jizz视频在线观看 | 日韩一区二区在线观看视频 | 一个色综合亚洲色综合 | 人成在线观看 | 国产乡下妇女做爰视频 | 天天欲色| 欧美操穴 | 国产成人免费一区二区三区 | 欧美肥妇视频 | 亚洲a麻豆乱潮 | 绝顶高潮合集videos | 欧美视频在线一区二区三区 | 无尽3d精品hentai在线视频 | 国产老女人乱淫免费可以 | 亚洲精品福利网站 | 国产精品久久久久久久久妇女 | 一级免费看视频 | 中文字幕无码不卡免费视频 | 国产做a爱片久久毛片 | 大学生女人三级在线播放 | 久久久久99精品成人片三人毛片 | 亚洲成网 | 国产区欧美区日韩区 | 人人妻人人澡人人爽不卡视频 | 丁香婷婷综合网 | 天天躁天天狠天天透 | 日韩久久免费视频 | 午夜人妻久久久久久久久 | 国产精品国产精品国产专区不卡 | 欧美国产精品一区 | 亚洲妇熟xxxx妇色黄 | av免费毛片| 乱人伦中文视频在线观看 | 国产偷人妻精品一区二区在线 | 黑人巨大精品欧美一区二区 | 丰满人妻熟妇乱偷人无码 | 午夜论坛 | 亚洲v欧美v | 激情内射亚州一区二区三区爱妻 | 少妇口述疯狂刺激的交换经历 | 亚洲精品乱码久久久久久蜜桃麻豆 | 国产精品久久久久久av | 黄色一级黄色片 | 一级黄色网 | 在线欧美国产 | 亚洲精品午夜国产va久久成人 | 羞羞答答国产xxdd亚洲精品 | 中文字幕首页 | 91精品国产综合久久精品性色 | 无码午夜福利片 | 爆乳女仆高潮在线观看 | 久久品道一品道久久精品 | 天堂av无码av一区二区三区 | 欧美gv在线观看 | 浴室人妻的情欲hd三级国产 | 美女久久久久久久久 | 国产黑丝91 | 奇米网狠狠干 | www.天天色 | 亚洲国产精品色拍网站 | 亚l州综合另中文字幕 | 欧洲成人在线观看 | av小四郎最新地址入口 | 视频一区二区欧美 | 欧美xxxx免费虐 | 蜜桃精品视频在线观看 | 国内精品视频 | 综合五月激情二区视频 | 欧美日韩3p | 少妇粉嫩小泬喷水视频www | 黄片毛片免费在线观看 | 天堂网在线播放 | 久久精品夜夜夜夜夜久久 | 秋霞一级黄色片 | 精品国产百合女同互慰 | 国产午夜激情视频 | 波多野结衣丝袜ol在线播放 | 手机在线看片 | 国产亚洲精品久久久久久国模美 | 久草青青草| 日韩三级黄色 | 欧美激情亚洲激情 | 免费看黄色一级片 | 裸体丰满少妇xxxxxxxx | 国产精品国产三级国产专区51 | 极品少妇嫩玉门av | 久综合| 午夜dj在线观看免费视频 | 日韩熟女精品一区二区三区 | 韩国乱码片免费看 | 水蜜桃色314在线观看 | 欧美亚洲色综久久精品国产 | 九九国产精品入口麻豆 | 亚洲人成人无码www 国产亚洲精品久久久久秋霞 | 国产精品理论片在线观看 | 国产免费视频一区二区裸体 | 亚洲一区在线看 | 亚洲 精品 主播 自拍 | 尤妮丝大尺度av在线播放 | 日韩精品视频在线看 | 国产综合在线视频 | 国产 欧美 精品 | 久久99精品国产.久久久久 | 欧美爱爱小视频 | 免费看aaaaa级少淫片 | 国产精品成人3p一区二区三区 | 久久久男人天堂 | 中文字幕女同女同女同 | 国产麻豆精品传媒 | 欧美一区二区视频在线 | 成人亚洲精品 | 免费看一级黄色大全 | 淫片一级国产 | 老司机午夜免费福利 | 偷拍自中文字av在线 | 国产高清黄色片 | 国产欧美一区二区三区网站 | 激情内射亚州一区二区三区爱妻 | 国产色在线 | 国产 狠狠色噜噜狠狠狠狠7777米奇 | 天堂一区二区三区 | 免费一级毛片在线观看 | 日韩伦理一区二区 | 日韩av在线播放观看 | 亚洲精品一区二区三区樱花 | 青青草欧美 | 久久综合九九 | 爆乳熟妇一区二区三区霸乳 | 国产又粗又深又猛又爽又在线观看 | 波多野结衣电车痴汉 | 国产黄大片在线观看画质优化 | 亚洲va一区二区 | 亚洲品牌自拍一品区9 | 韩国少妇bbb毛毛片 韩国少妇xxxx搡xxxx搡 | 日韩人妻无码免费视频一区二区三区 | 日韩综合在线 | 狂虐性器残忍蹂躏 | 国产丰满大乳奶水在线视频 | 久久亚洲网站 | 九色在线观看 | 精品国产18久久久久久怡红 | 精品无码国产一区二区三区麻豆 | 欧美激情一区二区三区 | 日韩精品久久久免费观看夜色 | 四十五十老熟妇乱孑视频 | 寡妇高潮一级视频免费看 | 日韩特级片 | 久久精品2 | 日韩a级一片 | 二男一女一级一片 | 成人久久视频 | 国产乱人对白 | 日韩av网站在线 | 国产亚洲性欧美日韩在线观看软件 | 亚洲最大看欧美片网站 | 综合在线一区 | 国产女教师bbwbbwbbw | 欧美三级久久久 | 免费看涩涩视频软件 | 午夜时刻免费入口 | 国产欧美一区二区三区鸳鸯浴 | 日韩中文字幕免费 | 国产农村妇女毛片精品久久 | 亚洲免费色视频 | 自拍偷自拍亚洲精品播放 | 国产精品一色哟哟 | 亚洲日本久久久 | 国产区在线观看视频 | 水蜜桃91 | 伊人成年网站综合网 | 色葡萄影院 | 成年人在线视频网站 | 小视频国产 | 91久久精品久久国产性色也91 | 日本aaa级片 | 91久久精品一区二区别 | 在线观看国产视频 | 91亚瑟视频 | yzzavcom免费观看视频 | 亚洲一区二区色 | 依人在线视频 | 国产又色又爽又黄刺激视频免费 | 亚洲精品无线乱码一区 | 国产中文久久 | 撕开少妇奶罩疯狂揉吮 | 女人大p毛片女人大p毛片 | www奇米影视com| 亚洲色图欧美日韩 | 456亚洲视频| 亚洲va韩国va欧美va精品 | 日韩网红少妇无码视频香港 | 三上悠亚人妻中文字幕在线 | 国产精品jizz在线观看老狼 | 一区二区三区四区不卡 | 久久久久亚洲精品国产 | 日本人与黑人做爰视频网站 | 国产老女人精品毛片久久 | 在线看一区 | xx中文字幕乱偷avxx | 成人无码精品1区2区3区免费看 | 又黄又爽又色无遮挡免费软件国外 | 天堂精品久久 | 欧美精品一区在线观看 | 97爱爱| 国产精品一区2区 | 日韩视频精品 | 国产91香蕉 | 成人三级iii | www.成人| 无遮挡又黄又刺激的视频 | 91操操| 少妇被粗大猛进进出出s小说 | 一级特黄录像免费观看 | 成人乱码一区二区三区av | 噜噜噜在线观看免费视频日本 | 欧美精品久久天天躁 | 不卡av在线 | 国产成人av一区二区在线观看 | av免费提供| 国产色妇| 国产经典一区 | 在线伊人网 | 成人精品一区二区三区视频播放 | 久久久区 | 欧美三级日本三级 | 欧美鲁鲁| 一区二区三区成人 | 黄色a级免费 | 黑人粗大猛烈进出高潮视频 | 91狠狠狠狠狠狠狠狠 | 久久无码精品一区二区三区 | 成人激情开心 | 久久久人体 | 亚洲综合另类小说色区色噜噜 | 亚洲欧美日韩成人高清在线一区 | 中文字幕3页| 欧美黄色片视频 | 小雪好紧好滑好湿好爽视频 | 制服丝袜美腿一区二区 | 鸥美毛片 | 国产一区二区三区视频 | 精品无码成人久久久久久 | 国产精品99久久久精品 | 亚洲国产精品无码一区二区三区 | 精品视频免费 | 色欲av永久无码精品无码 | 久久免费av| 久草免费福利视频 | 风韵多水的老熟妇 | 欧美色图日韩 | 无码一区二区三区在线 | 色猫咪免费人成网站在线观看 | 欧美三级午夜理伦三级 | 天天爽夜夜爽一区二区三区 | 自慰小少妇毛又多又黑流白浆 | 麻豆传传媒久久久爱 | 国产乱淫av片杨贵妃 | 国产裸体永久免费无遮挡 | 好吊妞视频一区二区三区 | 粉嫩一区二区三区色综合 | 精品无人乱码一区二区三区 | 麻豆av在线 | 亚洲最新中文字幕 | av中文字幕潮喷人妻系列 | 国产精品偷伦免费观看视频 | 天天躁久久躁日日躁 | 国产精品毛片一区二区 | 久久久久国产一区二区三区四区 | 欧美一区二区三区免费 | 伊人久久亚洲 | 日本妞xxxxxxxxx68| 尤物视频在线播放 | 日本视频在线免费观看 | 乱人伦人妻中文字幕无码久久网 | 亚洲国产图片 | 亚洲国产成人精品久久久国产成人 | 欧美成人91 | 欧美丰满熟妇xxxxx | 亚洲成色www8888 | 亚洲精品国产主播一区 | 成人香蕉视频 | 中国老女人内谢69xxxx | 美日韩丰满少妇在线观看 | 亚洲区免费中文字幕影片|高清在线观看 | 国产不卡在线观看视频 | 黄色三级生活片 | 婷婷精品久久久久久久久久不卡 | 欧美一页| 国产精品51麻豆cm传媒 | 色网站在线观看视频 | 成人性生交大片免费看r视频 | 这里只有精品免费视频 | 久久99精品久久久久婷婷 | 狠狠干精品 | 久久精品97| 成人综合区 | 91高清在线视频 | 青青草视频免费播放 | 一区二区三区不卡在线 | 亚洲中文字幕av无码区 | 正在播放木下凛凛xv99 | 欧美日韩片 | 国产精品99精品 | 亚洲国产精品麻豆 | 欧美激情欲高潮视频在线观看 | 中文字幕亚洲色图 | 精品国产自在久久现线拍 | 日本一级淫片免费啪啪3 | 日韩一级在线观看视频 | 青青青国产 | 国产四区 | 国产精品人人妻人人爽人人牛 | 国产亚洲精品精华液 | 国产精品一区二区麻豆 | 2021中文字幕在线观看 | 国产情侣久久久久aⅴ免费 精国产品一区二区三区a片 | 亚洲蜜桃av | av毛片网站 | 欧美国产成人精品一区二区三区 | 视频黄色免费 | 欧美成人ⅴideosxxxxx | 99热最新在线 | 成人在线观看国产 | 懂色av中文一区二区三区 | 香蕉视频三级 | aⅴ一区二区三区无卡无码 aⅴ在线免费观看 | 日韩夜夜| www夜夜爽| 日韩在线观看中文字幕 | 三级黄色视屏 | 亚州视频在线 | 古典武侠av | 免费看黄在线 | 亚洲国产欧美日韩在线精品一区 | 午夜男人天堂 | 免费看毛片网站 | 最新超碰 | 在线免费av片 | √最新版天堂资源网在线 | 亚洲熟妇av一区 | 亚洲男同视频 | 国产在线视频不卡 | 亚洲爆乳精品无码一区二区三区 | 婷婷久久香蕉五月综合加勒比 | 国产成人a亚洲精v品无码 | 拍拍拍产国影院在线观看 | 亚洲色图激情小说 | 青青草原成人网 | 亚洲一级二级片 | 亚洲 精品 综合 精品 自拍 | 天天爽夜夜爽夜夜爽精品视频 | 九一av| 91丨porny丨国产 | 蜜乳av懂色av粉嫩av | 啪网站 | 亚洲人成中文字幕在线观看 | 亚洲第一成人区av桥本有菜 | 美女销魂一区二区 | 亚洲国产综合色产精品色在线 | 中出 在线| 国产又爽又黄免费视频 | 亚洲男同志网站 | 国产98在线 | 日韩 | 午夜丁香婷婷 | av一区免费 | 老湿影院av | 毛片在线免费视频 | 国产精品综合色区在线观看 | 极品尤物一区二区三区 | 无码视频一区二区三区 | 性网站在线观看 | 日韩毛片免费观看 | 国产av无码国产av毛片 | 国产精品成人观看视频国产奇米 | 国产精品美女久久久久av爽李琼 | 毛片的网站 | 男女做激情爱呻吟口述全过程 | 日韩在线视频观看免费网站 | 丁香六月伊人 | 毛片天堂 | 亚洲最大国产成人综合网站 | 成人欧美一区二区三区黑人免费 | 免费网站在线高清观看 | 国产精品嫩草影院av蜜臀 | 97精品一区二区视频在线观看 | 久久久久久久影院 | 久热这里只有精品视频6 | 2023精品国色卡一卡二 | 国产精品视频免费丝袜 | 91丨九色丨喷水 | 91综合精品 | 91.久久 | 精品久久一区二区三区 | 亚洲国产精品入口 | 国产精品第 | 成人免费视频一区二区三区 | 特级精品毛片免费观看 | 色乱码一区二区三在线看 | 日韩激情在线观看 | 婷婷色影院| 99热这里有精品 | 狠狠色伊人亚洲综合第8页 狠狠色综合久久婷婷 | 黄色片xxxx | 大桥未久av一区二区三区中文 | 好吊视频在线观看 | av网子| 午夜色婷婷 | 女人喷液抽搐高潮视频 | 久久久久久亚洲国产精品 | 免费av资源在线观看 | 二区三区av | 一级片aaaa | 亚洲男人的天堂网 | 国产精品av一区 | 午夜天堂精品久久久久 | 香蕉视频免费在线播放 | 成人小网站 |