久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

您的位置:首頁技術(shù)文章
文章詳情頁

基于Spring Security前后端分離的權(quán)限控制系統(tǒng)問題

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

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

話不多說,入正題。一個(gè)簡單的權(quán)限控制系統(tǒng)需要考慮的問題如下:

權(quán)限如何加載 權(quán)限匹配規(guī)則 登錄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. 建表并生成相應(yīng)的實(shí)體類

基于Spring Security前后端分離的權(quán)限控制系統(tǒng)問題

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

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

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

菜單相當(dāng)于權(quán)限

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注解,因?yàn)锧Data包含@ToString注解

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

基于Spring Security前后端分離的權(quán)限控制系統(tǒng)問題

3. 自定義UserDetails

雖然可以使用Spring Security自帶的User,但是筆者還是強(qiáng)烈建議自定義一個(gè)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了,當(dāng)然要自己實(shí)現(xiàn)UserDetailsService了。這里當(dāng)時(shí)偷懶直接用自帶的User,后面放緩存的時(shí)候才知道不方便。

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; /** * 未認(rèn)證(未登錄)統(tǒng)一處理 * @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(); } }

未授權(quán)

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, '抱歉,您沒有權(quán)限訪問', 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 = '登錄超時(shí)或已在另一臺(tái)機(jī)器登錄,您被迫下線!'; 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處理

現(xiàn)在由于前后端分離,服務(wù)端不再維持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); // 存放自定義屬性,比如用戶擁有的權(quán)限 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,為此定義一個(gè)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 // 如果生存時(shí)間小于10分鐘,則再續(xù)1小時(shí) long time = stringRedisTemplate.getExpire(key); if (time < 600) { stringRedisTemplate.expire(key, (time + 3600), TimeUnit.SECONDS); } } } } chain.doFilter(request, response); } }

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

由于我們采用jwt生成token,因此沒法中途更改token的有效期,只能將其放到Redis中,通過更改Redis中key的生存時(shí)間來控制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'; } }

資源的訪問控制我們通過判斷是否有相應(yīng)的權(quán)限字符串

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前后端分離的權(quán)限控制系統(tǒng)問題

基于Spring Security前后端分離的權(quán)限控制系統(tǒng)問題

基于Spring Security前后端分離的權(quán)限控制系統(tǒng)問題

基于Spring Security前后端分離的權(quán)限控制系統(tǒng)問題

基于Spring Security前后端分離的權(quán)限控制系統(tǒng)問題

基于Spring Security前后端分離的權(quán)限控制系統(tǒng)問題

9. 補(bǔ)充:手機(jī)號+短信驗(yàn)證碼登錄

參照org.springframework.security.authentication.UsernamePasswordAuthenticationToken寫一個(gè)短信認(rèn)證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寫一個(gè)自己的短信認(rèn)證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 { // 校驗(yàn)驗(yàn)證碼 additionalAuthenticationChecks((SmsCodeAuthenticationToken) authentication); // 校驗(yàn)手機(jī)號 String mobile = authentication.getPrincipal().toString(); UserDetails userDetails = myUserDetailsService.loadUserByMobile(mobile); if (null == userDetails) { throw new BadCredentialsException('手機(jī)號不存在'); } // 創(chuàng)建認(rèn)證成功的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('驗(yàn)證碼不能為空'); } String mobile = authentication.getPrincipal().toString(); String smsCode = authentication.getCredentials().toString(); // 從Session或者Redis中獲取相應(yīng)的驗(yàn)證碼 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('短信驗(yàn)證碼不存在,請重新發(fā)送!'); } if (!smsCode.equalsIgnoreCase(verificationCode)) { throw new BadCredentialsException('驗(yàn)證碼錯(cuò)誤!'); } //todo 清除Session或者Redis中獲取相應(yīng)的驗(yàn)證碼 } @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寫一個(gè)短信認(rèn)證處理的過濾器

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中進(jìn)行配置

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前后端分離的權(quán)限控制系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于Spring Security權(quán)限控制系統(tǒng)的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Spring
相關(guān)文章:
主站蜘蛛池模板: 日本一区二区高清视频 | 国产精品久久久久久久岛一牛影视 | 精品视频在线观看 | av网站观看 | 9 1在线观看| 成人精品视频在线 | 亚洲欧美一 | 欧美视频在线免费看 | 国产一区久久 | 国产黄色一级大片 | 欧美日韩成人免费 | 凹凸日日摸日日碰夜夜爽孕妇 | 视频在线一区 | 国产在线观看一区 | 黄视频网站免费看 | 亚洲乱码国产乱码精品精的特点 | 成人日韩 | 狠狠狠干 | 一区二区中文字幕 | 日本不卡免费新一二三区 | 国产精品网址 | 日韩在线一区二区三区 | 在线欧美日韩 | 日韩中文视频 | 亚洲综合在线网 | 色婷婷亚洲一区二区三区 | 日韩a∨精品日韩在线观看 山岸逢花在线 | 九一视频在线观看 | 国产精品亚洲成在人线 | 在线观看免费黄色片 | 久久福利 | 欧美日韩在线观看一区二区 | 欧美激情综合色综合啪啪五月 | 亚洲av毛片一区二二区三三区 | 国产精品自产拍在线观看 | 国产香蕉视频在线播放 | 99精品电影 | 亚洲成人av一区二区 | 久久一本 | 99爱免费视频 | 天天干干 | 成人日韩| 99中文字幕| 精品久久一区二区 | 国产一区中文字幕 | 国产精品久久久久久一区二区三区 | 麻豆av电影在线观看 | 欧美一区二区三区四区视频 | 中文字字幕在线 | 射久久| 一级片网 | 久久国产精品一区 | 中文字幕一区二区三区四区 | 亚洲久草 | 亚洲电影在线观看 | 亚洲国产精品一区 | 国产中文字幕在线观看 | 亚洲国产精品久久久久秋霞蜜臀 | 亚洲一区二区免费看 | 少妇黄色 | 欧美精品片| 国产精品久久久久久久久久久久久久 | 毛片一级 | 综合网日韩 | 9色porny自拍视频一区二区 | 免费av一区二区三区 | 一区二区视频 | 天天澡天天狠天天天做 | 亚洲视频在线一区 | 国产高清在线精品一区二区三区 | 超碰中文字幕 | 91精品久久久久久久久久 | 黄色一级大片在线免费看产 | 99精品在线 | av中文字幕在线播放 | 欧美视频一区 | 欧美一级精品片在线看 | 国产特级毛片 | 一级毛片免费看 | 国产一区二区三区在线视频 | 精品久久久久久久久久久久包黑料 | 午夜久久 | 免费在线成人网 | 国产一级片在线 | 日韩成人高清电影 | 2022天天操 | 中文字幕在线网址 | 午夜视频免费网站 | 亚洲黄页 | 一级片观看 | 欧美在线观看网站 | 日日夜夜av | 天天天干夜夜夜操 | 国产传媒在线观看 | 高清国产一区二区三区 | 搞黄免费视频 | a在线免费观看 | 不卡久久 | 操操操av | av网站免费观看 | 国产性色 | 色一情一乱一伦一区二区三区 | 欧美成人h版在线观看 | 在线视频一区二区三区 | 日韩精品在线一区 | 蜜桃视频网站在线观看 | 久久久精品免费观看 | 日韩欧美综合在线 | 日韩2区| 国产精产国品一二三产区视频 | 中文字幕观看 | 婷婷毛片 | av观看免费 | www.中文字幕在线 | 精品中文字幕在线观看 | 人妖一区 | 午夜艹| 亚洲精品中文字幕 | 91精品一区二区三区久久久久久 | 精品免费久久 | 毛片a片 | 久久国产精品一区二区 | 青青99 | 91久久夜色精品国产网站 | 国产精品一区免费在线观看 | 精品二区 | 日韩av免费在线观看 | 国产性×xxx盗摄xxxx | 亚洲不卡视频 | 亚洲天堂中文字幕 | 看欧美黄色录像 | h片在线看 | 嫩草91 | 男女免费视频 | 美国特级a毛片免费网站 | 天天射射天天 | 中文字幕在线三区 | 怡红院成人影院 | 国产在线一二三区 | 欧美一区二区精品 | 骚视频在线观看 | 午夜小视频免费 | 狠狠干av| 韩国精品免费视频 | 日韩精品成人 | 欧美日韩电影一区二区三区 | 91精品国产91久久久久久吃药 | 亚洲第一区在线 | 国产成人精品一区二区三区四区 | 国精日本亚洲欧州国产中文久久 | 黄色精品网站 | 毛片网站在线 | 一级免费黄色 | 99久久精品免费看国产一区二区三区 | 成人精品久久 | 亚洲国产精品久久久久久 | av黄色一级| 成人片网址| 99国产精品99久久久久久 | www.久草| 你懂的在线视频播放 | 久久在线播放 | 激情图区在线观看 | 久久亚洲视频 | 91伊人| 日韩视频一区 | 国产激情在线视频 | 精品一区二区在线观看 | 免费日韩精品 | 日韩欧美二区 | 国产激情综合五月久久 | 久久久久久亚洲 | 99久久99久久久精品色圆 | 干一干操一操 | 亚洲视频一区在线播放 | 国产精品久久久一区二区三区 | 狠狠搞狠狠操 | 国产伦精品一区二区三区四区视频 | 免费三片在线观看网站 | 亚洲午夜精品一区二区三区他趣 | 二区在线观看 | 91精品国产综合久久久久久蜜月 | 精品免费| 日韩高清av| 亚洲福利在线观看 | 91精品久久久久久久久 | 精品成人免费一区二区在线播放 | 欧美成人精品一区二区男人看 | 国产中文字幕一区 | 国产精品久久久久久久久晋中 | 亚洲视频一区二区在线 | 国产精品a一区二区三区网址 | 狠狠插狠狠操 | 国产九九精品视频 | 欧美成人一区二区三区片免费 | 国产午夜视频在线观看 | 免费的一级毛片 | 欧美性一级 | 欧美福利在线 | 国产精品高清在线 | 亚洲国产精品久久久久秋霞不卡 | 国产超碰人人模人人爽人人添 | 欧美一区二区三区精品 | 日韩中文视频 | 一本一道久久久a久久久精品91 | 成人综合视频在线 | 日韩视频在线观看一区 | 国产片在线观看 | 亚洲国产婷婷香蕉久久久久久99 | 日韩视频在线免费 | 国产精品国产三级国产aⅴ入口 | 成人精品在线视频 | 成人欧美一区二区三区色青冈 | 99热首页 | 欧美日一区二区 | 亚洲一区播放 | 日韩av在线中文字幕 | 亚洲一区二区高清视频 | 夜夜艹日日艹 | 成人午夜视频在线观看 | 国产日韩精品入口 | 午夜精品一区二区三区在线视频 | 欧美日韩黄 | 免费看片一区二区三区 | a在线v| 最新日韩av网址 | 欧美精品乱码久久久久久按摩 | 久久精品一区二区三区四区毛片 | 欧美午夜一区二区三区免费大片 | 国产在视频一区二区三区吞精 | 草久在线视频 | 欧美一区二区三区免费 | 精品成人在线 | 日韩小视频在线播放 | 日本久久久久久久久久久久 | 日韩国产一区二区 | 81精品国产乱码久久久久久 | aaa在线观看 | 人人射人人草 | 精品国产三级 | 久久久一区二区 | 国产精品久久久久久久久久免费看 | 91看片淫黄大片一级在线观看 | 成年人在线视频播放 | 亚洲免费综合 | 精品欧美日韩 | 精品久久久久国产免费 | 亚洲精品久久久久久一区二区 | 91精品国产一区二区三区免费 | 中文字幕在线看第二 | 免费观看视频www | 亚洲国产成人精品女人 | 亚洲人成人一区二区在线观看 | 久久久综合视频 | 久草视| 日本特黄特色aaa大片免费 | 91在线免费观看 | 99久久视频 | 亚洲成人影院在线观看 | 久久国产精品久久 | 日本一区二区三区免费观看 | www.色在线 | 国产精品一区一区三区 | 久久久网 | 波多野结衣一区二区三区高清 | 银杏成人影院在线观看 | 91香蕉| 精品国产一区二区三区久久久蜜月 | 欧美日韩免费 | 国产精品二区一区二区aⅴ污介绍 | 黄色片免费在线观看 | 欧美激情欧美激情在线五月 | 天天干狠狠干 | 免费毛片在线播放 | 成人免费视频网站在线看 | 中文字幕在线免费视频 | 国产探花在线精品一区二区 | 国产农村妇女精品一二区 | 国产国拍亚洲精品av | 久久国产精品一区二区 | 亚洲日本欧美日韩高观看 | 国产精品国产三级国产aⅴ9色 | 精品久久久久久久久久久 | 亚洲一本 | 久久久91精品国产一区二区三区 | 欧美国产在线观看 | 国产情侣小视频 | 国产高潮失禁喷水爽网站 | 91在线视频福利 | 91亚洲视频 | 国产精品综合久久 | 日韩免费av网站 | 国产高清在线精品 | 亚洲一区中文字幕 | 成人1区| 国产精品一区二区三 | 国产在线一区二区三区 | 免费黄色在线 | 亚洲天堂中文字幕 | 成人亚洲视频 | 日韩精品久久久久久 | 日本一区视频在线观看 | 欧美日韩在线观看中文字幕 | 午夜精 | 日韩欧美国产一区二区三区 | 国产在线中文字幕 | 国产91在线视频 | 日韩一级片 | 99亚洲| 国产资源视频在线观看 | 久久亚洲国产精品 | 在线视频一二区 | 久久国产欧美日韩精品 | 日韩一二区视频 | 曰曰操 | 凹凸日日摸日日碰夜夜爽孕妇 | 亚洲aaa在线观看 | 9999国产精品欧美久久久久久 | 国产一区二区资源 | 国产一在线 | 日韩福利在线 | 国产日韩精品入口 | 中文字幕在线电影 | 国产免费一区二区 | 国产福利一区二区 | 亚洲综合精品 | www.蜜桃av | 天天操操| 国产精品久久久久久久娇妻 | 亚洲精品99| 国产老女人精品毛片久久 | 久久久久成人精品 | 韩日精品 | 欧美一区二区三区免费观看视频 | 国产免费自拍 | 精品国产精品国产偷麻豆 | 在线日韩| 久久高清片 | 最近日韩中文字幕 | 国产日韩视频在线播放 | 国产精品免费视频一区 | 欧美精品网站 | 欧美久久一区二区三区 | 日韩视频一区在线观看 | 国产成人一区 | 九色91视频| 日韩欧美精品一区二区三区 | 久草在线视频网 | 亚洲欧美国产一区二区三区 | www.天天草 | 国产特一级黄色片 | 91视频一区二区三区 | 精品无码久久久久久国产 | 国产精品成人国产乱一区 | 中文字幕亚洲欧美日韩在线不卡 | 免费a爱片猛猛 | 久久视频国产 | 99国产视频 | 免费看的av | 2024天天干 | 久草在线高清 | 亚洲精品一区二区三区蜜桃久 | 欧洲亚洲精品久久久久 | 999精品在线| 日本不卡高字幕在线2019 | 国产精品久久久久久福利一牛影视 | 亚洲欧美日韩精品久久亚洲区 | 免费观看毛片 | 日韩免费区 | 亚洲久久 | 日韩一级大片 | 久久久国产精品x99av | 久热热热 | 琪琪午夜伦伦电影福利片 | 亚洲欧美中文日韩在线v日本 | 欧美在线xxx | 色网在线 | 久久精品福利 | 久草免费在线 | 福利亚洲 | 在线色网站 | 国产免费av在线 | 欧美日韩一区免费 | 中文字幕在线观看一区二区三区 | 人人草天天草 | 久久久久一区二区 | 国产午夜视频 | 欧美日韩国产在线播放 | 91久久精品一区二区三区 | 精品久久久久久久久久久久久久 | 成人午夜啪啪好大 | 狠狠色丁香婷婷综合 | 成人免费视屏 | 欧美一区二区日韩 | 欧美色性 | 亚洲永久精品www | 久久久精品视频免费观看 | 久久国产精品一区 | 五月婷婷色| 一区二区三区日韩 | 91精品国产日韩91久久久久久 | 黄色影视在线免费观看 | 91精品视频在线播放 | 日韩在线1 | 操网| 国产在亚洲 线视频播放 | 国产精品国产精品国产专区不片 | 国产成人精品在线 | 在线观看污片 | 久久中文字幕视频 | 亚洲欧美国产一区二区三区 | 日韩免费网站 | 国产精品久久久久国产a级 91福利网站在线观看 | 中文在线观看www | 成人免费视频观看视频 | 色毛片| 视频在线91| 成人涩涩日本国产一区 | 一级毛片在线看aaaa | 日韩免费一区 | 国产一区二区视频在线观看 | 黄色电影天堂 | 亚洲欧美日韩国产 | 日韩精品一区二区三区在线观看 | 国产精品国色综合久久 | 99亚洲| 成人小视频在线观看 | 久久综合香蕉 | 亚洲在线视频 | 中国妞xxxhd露脸偷拍视频 | 欧美激情精品久久久久久免费 | 日日干夜夜干 | 永久精品 | 国产精品999| 精品不卡 | 亚洲第一成人在线视频 | 成人在线视频一区 | 亚洲午夜精品一区二区三区他趣 | 特级淫片裸体免费看 | 免费日本视频 | 久久99深爱久久99精品 | 不卡中文一区 | 欧美日本韩国一区二区三区 | 国产一级特黄毛片在线毛片 | 操操操影院 | 精品久久久久久久久久久久久久 | 亚洲欧美日韩精品 | 自拍视频网站 | 国产在线观看av | 精品视频久久 | 国产精品视频播放 | 在线成人一区 | 一级全黄少妇性色生活片毛片 | 99中文字幕 | 97碰碰碰| 国产毛片一区二区 | 成人在线手机版视频 | 免费精品| 亚洲欧美一区二区精品中文字幕 | 午夜精品一区二区三区四区 | 午夜精品久久久久久久久久久久久 | 欧美一区二区精品 | 日韩一二三区 | 欧美久久久久久久久久久久久久 | 2020国产在线 | 一区二区日韩 | 啵啵羞羞影院 | 成人精品二区 | 艹逼网 | 热久久久久| www.99日本精品片com | 久操成人 | 日韩电影在线 | 久久久网站 | 日本在线观看 | 久久久婷| 国产一级免费网站 | 亚洲欧洲日韩 | 亚洲精品一区二区三区 | 一区二区三区免费网站 | 四季久久免费一区二区三区四区 | 欧美一级二级三级视频 | 欧洲一区二区三区 | 国产日韩欧美91 | 国产成人在线视频 | 欧美成人高清视频 | 成人a在线视频免费观看 | 欧美国产视频 | 看毛片的网站 | 国产视频一区二区在线 | 久久久久亚洲av毛片大全 | 91黄在线观看| 欧美国产精品一区 | 免费一区二区三区 | 天天澡天天狠天天天做 | 久久久精品久久久 | 日韩精品在线视频观看 | av一区二区三区四区 | 亚洲综合日韩 | 高清一区二区 | av性色| 91精品综合久久久久久五月天 | 啵啵影院午夜男人免费视频 | 国产黄色网址在线观看 | 久久少妇免费看 | 免费成人在线观看视频 | 色悠久久久 | 国产综合一区二区 | 一区二区三区四区免费观看 | 国产精品白浆 | 成人av在线网 | 一区福利| 亚洲国产精品成人 | 成人水多啪啪片 | 精品久久久久久久久久久久久久久久久久久 | 天天爽夜夜春 | 久久精品国产亚洲一区二区三区 | 天堂va在线高清一区 | 毛片免费观看视频 | 一级片在线观看 | 国产精品丝袜视频 | 精品久久久久久久人人人人传媒 | 日韩精品视频免费专区在线播放 | 黄色小视频在线观看 | 91精品国产综合久久福利软件 | 欧美精品一区二区蜜臀亚洲 | 成人亚洲一区 | 伊人伊人伊人 | 欧美成人毛片 | 亚洲成人黄色 | 中文字幕第一页在线 | 亚洲最黄网站 | 国产精品久久久久久一区二区三区 | 国产精品视频一二 | 福利片中文字幕 | 国产日韩精品视频 | 亚洲综合一区二区三区 | 亚洲男人天堂 | 欧美一区二区另类 | 欧美一区二区伦理片 | 亚洲精品久久久久久下一站 | 综合伊人| 国产精品美女在线观看 | 情一色一乱一欲一区二区 | 亚洲成人一区二区三区 | 中文字幕视频在线免费 | 成人在线播放 | 欧美一级在线 | 日韩在线观看视频一区二区三区 | 一区二区三区视频播放 | 欧美日韩一区精品 | 午夜精品久久久久久久久久久久久 | 97久久超碰| 日本三级在线视频 | 色狠狠一区 | 日韩一区二区三区四区五区 | 欧美激情在线播放 | 91精品一区二区三区久久久久 | 日韩精品视频在线播放 | 精品日韩一区二区三区 | 在线不卡一区 | 久草资源在线视频 | 91精品国产综合久久久蜜臀图片 | 午夜精品网站 | 精品国产一区一区二区三亚瑟 | 亚洲国产精品一区 | 成人亚洲一区 | 欧美狠狠操 | 日韩成人在线观看 | 婷婷色视频| 精品国产乱码久久久久久久软件 | 密室大逃脱第六季大神版在线观看 | 久色视频在线观看 | 色播99 | 成人在线高清 | 91在线视频播放 | 亚洲福利一区二区 | 久久一区二区视频 | 草樱av | 日韩精品极品视频在线 | 久久男人天堂 | www.com欧美| 精品国产91亚洲一区二区三区www | 国产高清在线a视频大全 | 天天躁日日躁狠狠躁av麻豆 | 国产 欧美 日韩 一区 | 久久久精| 性色爽爱| 国产视频1 | 久久久中文字幕 | 狠狠久久婷婷 | 精品一区视频 | 日韩素人在线 | 天天操天天干天天插 | 中文字幕日韩一区二区 | 国产成人午夜精品5599 | 日韩成人在线观看视频 | 精品无人乱码一区二区三区 | 欧美在线一区二区 | 中字幕视频在线永久在线观看免费 | 精品国产91亚洲一区二区三区www | 91精品国产色综合久久 | 91久久91久久精品免观看 | 在线色站| 日韩中文一区二区三区 | 精品国产伦一区二区三区观看说明 | 久久久一区二区三区 | 精品在线播放 | 日韩在线一区二区三区 | 91色电影| 亚洲国产欧美日韩 | 国产福利一区二区三区四区 | 天天操天操 | 一区二区免费视频 | 精品视频二区 |