<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Security Web 權限方案

          共 42498字,需瀏覽 85分鐘

           ·

          2021-04-25 10:11

          點擊上方藍色字體,選擇“標星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

            作者 |  我係死肥宅 

          來源 |  urlify.cn/fiqQ3y

          76套java從入門到精通實戰(zhàn)課程分享

          3.1 設置登錄系統(tǒng)的賬號、密碼

          方式一:在 application.properties

          spring.security.user.name=atguigu
          spring.security.user.password=atguigu

          方式二:編寫類實現(xiàn)接口

          package com.atgugui.securitydemo1.config;

          import org.springframework.context.annotation.Configuration;
          import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
          import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
          import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

          @Configuration
          public class SecurityConfig extends WebSecurityConfigurerAdapter {
              @Override
              protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                  BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
                  String password = encoder.encode("123");

                  auth.inMemoryAuthentication()
                          .passwordEncoder(encoder)
                          .withUser("lucy")
                          .password(password)
                          .roles("admin");
              }
          }

          方式三:自定義編寫實現(xiàn)類

          package com.atgugui.securitydemo1.config;

          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
          import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
          import org.springframework.security.core.userdetails.UserDetailsService;
          import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
          import org.springframework.security.crypto.password.PasswordEncoder;

          @Configuration
          public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
              @Autowired
              private UserDetailsService userDetailsService;

              @Override
              protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                  auth.userDetailsService(userDetailsService)
                          .passwordEncoder(password());
              }

              @Bean
              PasswordEncoder password() {
                  return new BCryptPasswordEncoder();
              }
          }

          package com.atgugui.securitydemo1.service;

          import org.springframework.security.core.GrantedAuthority;
          import org.springframework.security.core.authority.AuthorityUtils;
          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.security.crypto.bcrypt.BCryptPasswordEncoder;
          import org.springframework.stereotype.Service;

          import java.util.List;

          @Service("userDetailsService")
          public class MyUserDetailService implements UserDetailsService {
              @Override
              public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                  List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
                  return new User("mary", new BCryptPasswordEncoder().encode("123"), auths);
              }
          }

          3.2 實現(xiàn)數(shù)據(jù)庫認證來完成用戶登錄

          完成自定義登錄

          3.2.1 準備 sql

          CREATE TABLE users (
              id BIGINT PRIMARY KEY AUTO_INCREMENT,
              username VARCHAR(20) UNIQUE NOT NULL,
              password VARCHAR(100)
          );

          INSERT INTO users
          VALUES (1, 'lucy''123');

          INSERT INTO users
          VALUES (2, 'mary''456');

          3.2.2 添加依賴

          <dependencies>
              <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>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
                  <scope>test</scope>
                  <exclusions>
                      <exclusion>
                          <groupId>org.junit.vintage</groupId>
                          <artifactId>junit-vintage-engine</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              <dependency>
                  <groupId>org.springframework.security</groupId>
                  <artifactId>spring-security-test</artifactId>
                  <scope>test</scope>
              </dependency>

              <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
              <dependency>
                  <groupId>com.baomidou</groupId>
                  <artifactId>mybatis-plus-boot-starter</artifactId>
                  <version>3.4.2</version>
              </dependency>
              <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>8.0.23</version>
              </dependency>
              <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <version>1.18.20</version>
                  <scope>provided</scope>
              </dependency>
          </dependencies>

          3.2.3 制作實體類

          package com.atgugui.securitydemo1.entity;

          import lombok.Data;

          @Data
          public class Users {
              /**
               * id
               */
              private Integer id;

              /**
               * 用戶名
               */
              private String username;

              /**
               * 密碼
               */
              private String password;
          }

          3.2.4 整合 MybatisPlus 制作 mapper

          package com.atgugui.securitydemo1.mapper;

          import com.atgugui.securitydemo1.entity.Users;
          import com.baomidou.mybatisplus.core.mapper.BaseMapper;
          import org.springframework.stereotype.Repository;

          @Repository
          public interface UsersMapper extends BaseMapper<Users> {
          }

          配置文件添加數(shù)據(jù)庫配置

          # mysql 數(shù)據(jù)庫連接
          spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
          spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
          spring.datasource.username=root
          spring.datasource.password=12345678

          3.2.5 制作登錄實現(xiàn)類

          package com.atgugui.securitydemo1.service;

          import com.atgugui.securitydemo1.entity.Users;
          import com.atgugui.securitydemo1.mapper.UsersMapper;
          import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.security.core.GrantedAuthority;
          import org.springframework.security.core.authority.AuthorityUtils;
          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.security.crypto.bcrypt.BCryptPasswordEncoder;
          import org.springframework.stereotype.Service;

          import java.util.List;

          @Slf4j
          @Service("userDetailsService")
          public class MyUserDetailService implements UserDetailsService {
              @Autowired
              private UsersMapper usersMapper;

              @Override
              public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                  // 根據(jù)用戶名查詢
                  QueryWrapper<Users> wrapper = new QueryWrapper<>();
                  wrapper.eq("username", username);
                  Users users = usersMapper.selectOne(wrapper);
                  log.info("[Service]users:{}", users);
                  if (users == null) {
                      throw new UsernameNotFoundException("用戶名不存在!");
                  }
                  System.out.println(users);
                  List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
                  return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
              }
          }

          3.2.6 添加注解

          package com.atgugui.securitydemo1;

          import org.mybatis.spring.annotation.MapperScan;
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;

          @SpringBootApplication
          @MapperScan("com.atgugui.securitydemo1.mapper")
          public class Securitydemo1Application {

              public static void main(String[] args) {
                  SpringApplication.run(Securitydemo1Application.class, args);
              }

          }

          3.2.7 啟動測試

          2021-04-17 23:35:55.905  INFO 11768 --- [nio-8111-exec-5] c.a.s.service.MyUserDetailService        : [Service]users:Users(id=1, username=lucy, password=123)

          3.3 未認證請求跳轉(zhuǎn)到登錄頁

          3.3.1 編寫頁面

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <form action="/user/login" method="post">
              用戶名:<input type="text" name="username"><br>
              密碼:<input type="password" name="password"><br>
              <input type="submit" value="login">
          </form>
          </body>
          </html>

          3.3.2 編寫控制器

          package com.atgugui.securitydemo1.controller;

          import org.springframework.web.bind.annotation.GetMapping;
          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.bind.annotation.RestController;

          @RestController
          @RequestMapping("/test")
          public class TestController {
              @GetMapping("hello")
              public String hello() {
                  return "hello security";
              }

              @GetMapping("index")
              public String index() {
                  return "hello index";
              }
          }

          3.3.3 編寫配置類放行登錄頁面以及靜態(tài)資源

          package com.atgugui.securitydemo1.config;

          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
          import org.springframework.security.config.annotation.web.builders.HttpSecurity;
          import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
          import org.springframework.security.core.userdetails.UserDetailsService;
          import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
          import org.springframework.security.crypto.password.PasswordEncoder;

          @Configuration
          public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
              @Autowired
              private UserDetailsService userDetailsService;

              @Override
              protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                  auth.userDetailsService(userDetailsService)
                          .passwordEncoder(password());
              }

              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.formLogin()    // 自定義編寫的登錄頁面
                          .loginPage("/login.html")   // 登錄頁面設置
                          .loginProcessingUrl("/user/login")  // 登錄訪問路徑
                          .defaultSuccessUrl("/test/index").permitAll()    // 登錄成功之后,跳轉(zhuǎn)路徑
                          .and().authorizeRequests()
                              .antMatchers("/""/test/hello""user/login").permitAll()  // 直接訪問,不需要認證
                          .anyRequest().authenticated()
                          .and().csrf().disable();    // 關閉csrf防護
              }

              @Bean
              PasswordEncoder password() {
                  return new BCryptPasswordEncoder();
              }
          }

          3.3.4 測試

          3.4 基于角色或權限進行訪問控制

          3.4.1 hasAuthority 方法

          如果當前的主體具有指定的權限,則返回 true,否則返回 false

          修改配置類

          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.formLogin()    // 自定義編寫的登錄頁面
                      .loginPage("/login.html")   // 登錄頁面設置
                      .loginProcessingUrl("/user/login")  // 登錄訪問路徑
                      .defaultSuccessUrl("/test/index").permitAll()    // 登錄成功之后,跳轉(zhuǎn)路徑
                      .and().authorizeRequests()
                          .antMatchers("/""/test/hello""user/login").permitAll()  // 直接訪問,不需要認證
                          .antMatchers("/test/index").hasAuthority("admins")  // 權限
                          .anyRequest().authenticated()
                      .and().csrf().disable();    // 關閉csrf防護
          }

          添加一個控制器

          @GetMapping("index")
          public String index() {
              return "hello index";
          }

          給用戶登錄主體賦予權限

          測試

          role

          admins

          3.4.2 hasAnyAuthority 方法

          如果當前的主體有任何提供的角色(給定的作為一個逗號分隔的字符串列表)的話,返回 true

          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.formLogin()    // 自定義編寫的登錄頁面
                      .loginPage("/login.html")   // 登錄頁面設置
                      .loginProcessingUrl("/user/login")  // 登錄訪問路徑
                      .defaultSuccessUrl("/test/index").permitAll()    // 登錄成功之后,跳轉(zhuǎn)路徑
                      .and().authorizeRequests()
                          .antMatchers("/""/test/hello""user/login").permitAll()  // 直接訪問,不需要認證
                          .antMatchers("/test/index").hasAnyAuthority("admins,manager")  // 權限
                          .anyRequest().authenticated()
                      .and().csrf().disable();    // 關閉csrf防護
          }

          3.4.3 hasRole 方法

          如果用戶具備給定角色就允許訪問,否則出現(xiàn) 403。如果當前主體具有指定的角色,則返回 true。

          private static String hasRole(String role) {
             Assert.notNull(role, "role cannot be null");
             if (role.startsWith("ROLE_")) {
                throw new IllegalArgumentException(
                      "role should not start with 'ROLE_' since it is automatically inserted. Got '"
                            + role + "'");
             }
             return "hasRole('ROLE_" + role + "')";
          }

          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.formLogin()    // 自定義編寫的登錄頁面
                      .loginPage("/login.html")   // 登錄頁面設置
                      .loginProcessingUrl("/user/login")  // 登錄訪問路徑
                      .defaultSuccessUrl("/test/index").permitAll()    // 登錄成功之后,跳轉(zhuǎn)路徑
                      .and().authorizeRequests()
                          .antMatchers("/""/test/hello""user/login").permitAll()  // 直接訪問,不需要認證
                          // .antMatchers("/test/index").hasAnyAuthority("admins,manager")  // 權限
                          .antMatchers("/test/index").hasRole("sale")
                          .anyRequest().authenticated()
                      .and().csrf().disable();    // 關閉csrf防護
          }

          @Override
          public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
              // 根據(jù)用戶名查詢
              QueryWrapper<Users> wrapper = new QueryWrapper<>();
              wrapper.eq("username", username);
              Users users = usersMapper.selectOne(wrapper);
              log.info("[Service]users:{}", users);
              if (users == null) {
                  throw new UsernameNotFoundException("用戶名不存在!");
              }
              List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
              return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
          }

          修改配置文件:注意配置文件中不需要添加”ROLE_“,因為上述的底層代碼會自動添加與之進行匹配。

          3.4.4 hasAnyRole

          表示用戶具備任何一個條件都可以訪問。

          3.5 自定義 403 頁面

          3.5.1 修改訪問配置類

          http.exceptionHandling().accessDeniedPage("/unauth.html");

          3.5.2 添加對應頁面

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <h1>沒有訪問權限</h1>
          </body>
          </html>

          3.5.3 測試

          3.6 注解使用

          3.6.1 @Secured

          判斷是否具有角色,另外需要注意的是這里匹配的字符串需要添加前綴“ROLE_“。

          使用注解先要開啟注解功能!

          import org.mybatis.spring.annotation.MapperScan;
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

          @SpringBootApplication
          @MapperScan("com.atgugui.securitydemo1.mapper")
          @EnableGlobalMethodSecurity(securedEnabled=true)
          public class Securitydemo1Application {

              public static void main(String[] args) {
                  SpringApplication.run(Securitydemo1Application.class, args);
              }

          }

          在控制器方法上添加注解

          @GetMapping("update")
          @Secured({"ROLE_sale""ROLE_manager"})
          public String update() {
              return "hello update";
          }

          登錄之后直接訪問控制器

          3.6.2 @PreAuthorize

          先開啟注解功能:@EnableGlobalMethodSecurity(prePostEnabled = true)

          @EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled = true)
          public class Securitydemo1Application {

          @PreAuthorize:注解適合進入方法前的權限驗證,@PreAuthorize可以將登錄用戶的roles/permissions參數(shù)傳到方法中

          @GetMapping("update")
          // @Secured({"ROLE_sale""ROLE_manager"})
          @PreAuthorize("hasAnyAuthority('admins')")
          public String update() {
              return "hello update";
          }

          ![image-20210418095133812](D:\我的堅果云\Spring Security\Spring Security Web 權限方案.assets\image-20210418095133812.png)

          3.6.3 @PostAuthorize

          開啟注解功能:@EnableGlobalMethodSecurity(prePostEnabled = true)

          @PostAuthorize 注解使用并不多,在方法執(zhí)行后再進行權限驗證,適合驗證帶有返回值的權限

          3.6.4 @PostFilter

          @PostFilter:權限驗證之后對數(shù)據(jù)進行過濾留下用戶名是admin1的數(shù)據(jù)

          表達式中的 filterObject 引用的是方法返回值List中的某一個元素

          @RequestMapping("getAll")
          @PostAuthorize("hasAnyAuthority('admins')")
          @PostFilter("filterObject.username == 'admin1'")
          public List<Users> getAllUser() {
              List<Users> list = Lists.newArrayList();
              list.add(new Users(1, "admin1""6666"));
              list.add(new Users(2, "admin2""888"));
              return list;
          }

          ![image-20210418100422414](D:\我的堅果云\Spring Security\Spring Security Web 權限方案.assets\image-20210418100422414.png)

          3.6.5 @PreFilter

          @PreFilter:進入控制器之前對數(shù)據(jù)進行過濾

          3.7 基于數(shù)據(jù)庫的記住我

          3.7.1創(chuàng)建表

          CREATE TABLE persistent_logins (
              username varchar(64) NOT NULL,
              series varchar(64) PRIMARY KEY,
              token varchar(64) NOT NULL,
              last_used timestamp NOT NULL
          )

          /** Default SQL for creating the database table to store the tokens */
          public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "
                + "token varchar(64) not null, last_used timestamp not null)";

          3.7.2 添加數(shù)據(jù)庫的配置文件

          spring:
            datasource:
              driver-class-name: com.mysql.jdbc.Driver
              url: jdbc:mysql://192.168.200.128:3306/test
              username: root
              password: root

          3.7.3 編寫配置類

          package com.atgugui.securitydemo1.config;

          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
          import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

          import javax.sql.DataSource;

          @Configuration
          public class BrowserSecurityConfig {
              @Autowired
              private DataSource dataSource;

              @Bean
              public PersistentTokenRepository persistentTokenRepository() {
                  JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
                  // 賦值數(shù)據(jù)源
                  jdbcTokenRepository.setDataSource(dataSource);
                  // 自動創(chuàng)建表,第一次執(zhí)行會創(chuàng)建,以后要執(zhí)行就要刪除掉!
                  // jdbcTokenRepository.setCreateTableOnStartup(true);
                  return jdbcTokenRepository;
              }
          }

          3.7.4 修改安全配置類

          @Autowired
          private UserDetailsService userDetailsService;
          @Autowired
          private PersistentTokenRepository tokenRepository;

          @Override
          protected void configure(HttpSecurity http) throws Exception {
              // 退出登錄
              http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();

              // 無權限
              http.exceptionHandling().accessDeniedPage("/unauth.html");

              http.formLogin()    // 自定義編寫的登錄頁面
                      .loginPage("/login.html")   // 登錄頁面設置
                      .loginProcessingUrl("/user/login")  // 登錄訪問路徑
                      .defaultSuccessUrl("/success.html").permitAll()    // 登錄成功之后,跳轉(zhuǎn)路徑
                      .and().authorizeRequests()
                          .antMatchers("/""/test/hello""user/login").permitAll()  // 直接訪問,不需要認證
                          // .antMatchers("/test/index").hasAnyAuthority("admins,manager")  // 權限
                          .antMatchers("/test/index").hasRole("sale1")
                          .anyRequest().authenticated()
                          .and().rememberMe().tokenRepository(tokenRepository)
                              .tokenValiditySeconds(60).userDetailsService(userDetailsService)
                      .and().csrf().disable();    // 關閉csrf防護
          }

          3.7.5 頁面添加記住我復選框

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <form action="/user/login" method="post">
              用戶名:<input type="text" name="username"><br>
              密碼:<input type="password" name="password"><br>
              記住我:<input type="checkbox" name="remember-me" title="記住密碼"><br>
              <input type="submit" value="login">
          </form>
          </body>
          </html>

          此處:name 屬性值必須位remember-me.不能改為其他值

          3.7.6 進行登錄測試

          登錄成功之后,關閉瀏覽器再次訪問發(fā)現(xiàn)依然可以使用!

          Cookie

          數(shù)據(jù)庫

          3.7.7 設置有效期

          默認2周時間。但是可以通過設置狀態(tài)有效時間,即使項目重新啟動下次也可以正常登錄。

          3.7.8 原理

          3.8 用戶注銷

          3.8.1 在登錄頁面添加一個退出鏈接

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          登錄成功!
          <a href="/logout">退出登錄</a>
          </body>
          </html>

          3.8.2 在配置類中添加退出映射地址

          @Override
          protected void configure(HttpSecurity http) throws Exception {
              // 退出登錄
              http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();

              // 無權限
              http.exceptionHandling().accessDeniedPage("/unauth.html");

              http.formLogin()    // 自定義編寫的登錄頁面
                      .loginPage("/login.html")   // 登錄頁面設置
                      .loginProcessingUrl("/user/login")  // 登錄訪問路徑
                      .defaultSuccessUrl("/success.html").permitAll()    // 登錄成功之后,跳轉(zhuǎn)路徑
                      .and().authorizeRequests()
                          .antMatchers("/""/test/hello""user/login").permitAll()  // 直接訪問,不需要認證
                          // .antMatchers("/test/index").hasAnyAuthority("admins,manager")  // 權限
                          .antMatchers("/test/index").hasRole("sale1")
                          .anyRequest().authenticated()
                      .and().csrf().disable();    // 關閉csrf防護
          }

          3.8.3 測試

          退出之后,是無法訪問需要登錄時才能訪問的控制器!

          3.9 CSRF

          3.9.1 CSRF 理解

          跨站請求偽造(英語:Cross-site request forgery),也被稱為one-click attack或者session riding,通常縮寫為CSRF或者XSRF,是一種挾制用戶在當前已登錄的Web應用程序上執(zhí)行非本意的操作的攻擊方法。跟跨網(wǎng)站腳本(XSS)相比,XSS利用的是用戶對指定網(wǎng)站的信任,CSRF 利用的是網(wǎng)站對用戶網(wǎng)頁瀏覽器的信任。

          跨站請求攻擊,簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經(jīng)認證過的網(wǎng)站并運行一些操作(如發(fā)郵件,發(fā)消息,甚至財產(chǎn)操作如轉(zhuǎn)賬和購買商品)。由于瀏覽器曾經(jīng)認證過,所以被訪問的網(wǎng)站會認為是真正的用戶操作而去運行。這利用了web中用戶身份驗證的一個漏洞:簡單的身份驗證只能保證請求發(fā)自某個用戶的瀏覽器,卻不能保證請求本身是用戶自愿發(fā)出的。

          從Spring Security 4.0開始,默認情況下會啟用CSRF保護,以防止CSRF攻擊應用程序,Spring Security CSRF會針對PATCH,POST,PUT和DELETE方法進行防護。

          3.9.2 Spring Security 實現(xiàn)CSRF的原理

          1. 生成csrfToken 保存到HttpSession 或者Cookie 中。

          package org.springframework.security.web.csrf;

          import java.io.Serializable;

          /**
           * Provides the information about an expected CSRF token.
           *
           * @see DefaultCsrfToken
           *
           * @author Rob Winch
           * @since 3.2
           *
           */
          public interface CsrfToken extends Serializable {

             /**
              * Gets the HTTP header that the CSRF is populated on the response and can be placed
              * on requests instead of the parameter. Cannot be null.
              *
              * @return the HTTP header that the CSRF is populated on the response and can be
              * placed on requests instead of the parameter
              */
             String getHeaderName();

             /**
              * Gets the HTTP parameter name that should contain the token. Cannot be null.
              * @return the HTTP parameter name that should contain the token.
              */
             String getParameterName();

             /**
              * Gets the token value. Cannot be null.
              * @return the token value
              */
             String getToken();

          SaveOnAccessCsrfToken類有個接口CsrfTokenRepository

          private static final class SaveOnAccessCsrfToken implements CsrfToken {
             private transient CsrfTokenRepository tokenRepository;
             private transient HttpServletRequest request;
             private transient HttpServletResponse response;

          當前接口實現(xiàn)類:HttpSessionCsrfTokenRepository,CookieCsrfTokenRepository

          /**
           * A {@link CsrfTokenRepository} that persists the CSRF token in a cookie named
           * "XSRF-TOKEN" and reads from the header "X-XSRF-TOKEN" following the conventions of
           * AngularJS. When using with AngularJS be sure to use {@link #withHttpOnlyFalse()}.
           *
           * @author Rob Winch
           * @since 4.1
           */
          public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
             static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN";

             static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";

          @Override
          public CsrfToken generateToken(HttpServletRequest request) {
             return new DefaultCsrfToken(this.headerName, this.parameterName,
                   createNewToken());
          }

          private String createNewToken() {
             return UUID.randomUUID().toString();
          }

          1. 請求到來時,從請求中提取csrfToken,和保存的csrfToken 做比較,進而判斷當前請求是否合法。主要通過CsrfFilter 過濾器來完成。

          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
             request.setAttribute(HttpServletResponse.class.getName(), response);
             CsrfToken csrfToken = this.tokenRepository.loadToken(request);
             boolean missingToken = (csrfToken == null);
             if (missingToken) {
                csrfToken = this.tokenRepository.generateToken(request);
                this.tokenRepository.saveToken(csrfToken, request, response);
             }
             request.setAttribute(CsrfToken.class.getName(), csrfToken);
             request.setAttribute(csrfToken.getParameterName(), csrfToken);
             if (!this.requireCsrfProtectionMatcher.matches(request)) {
                if (this.logger.isTraceEnabled()) {
                   this.logger.trace("Did not protect against CSRF since request did not match "
                         + this.requireCsrfProtectionMatcher);
                }
                filterChain.doFilter(request, response);
                return;
             }
             String actualToken = request.getHeader(csrfToken.getHeaderName());
             if (actualToken == null) {
                actualToken = request.getParameter(csrfToken.getParameterName());
             }
             if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
                this.logger.debug(
                      LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
                AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
                      : new MissingCsrfTokenException(actualToken);
                this.accessDeniedHandler.handle(request, response, exception);
                return;
             }
             filterChain.doFilter(request, response);
          }

          private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;

          /**
           * The default {@link RequestMatcher} that indicates if CSRF protection is required or
           * not. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other
           * requests.
           */
          public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher();

          private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {

             private final HashSet<String> allowedMethods = new HashSet<>(Arrays.asList("GET""HEAD""TRACE""OPTIONS"));

             @Override
             public boolean matches(HttpServletRequest request) {
                return !this.allowedMethods.contains(request.getMethod());
             }

             @Override
             public String toString() {
                return "CsrfNotRequired " + this.allowedMethods;
             }

          }

          3.9.3 案例

          在登錄頁面添加一個隱藏域

          <input type="hidden" th:if="${_csrf}!=null" th:value="${_csrf.token}" name="_csrf"/>

          關閉安全配置的類中的csrf

          // http.csrf().disable();





          鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布

          ??????

          ??長按上方微信二維碼 2 秒





          感謝點贊支持下哈 

          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色成人在线观看视频 | 青娱乐国产在线 | 午夜艹逼 | 日韩黄色视频在线 | 五月丁香激情视频 |