亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringBoot安全認證Security的實現(xiàn)方法

 更新時間:2019年05月29日 14:43:43   作者:柚子蘋果果  
這篇文章主要介紹了SpringBoot安全認證Security的實現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

一、基本環(huán)境搭建

父pom依賴

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.3.RELEASE</version>
</parent>

1. 添加pom依賴:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. 創(chuàng)建測試用Controller

@RestController
public class TestController {

  @GetMapping("getData")
  public String getData() {
    return "date";
  }

}

3. 創(chuàng)建SpringBoot啟動類并run

@SpringBootApplication
public class SpringBootTestApplication {

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

}

4. 測試

訪問http://127.0.0.1:8080/getData,由于我們開啟了SpringSecurity且當(dāng)前是未登錄狀態(tài),頁面會被302重定向到http://127.0.0.1:8080/login,頁面如下:

用戶名:user,密碼可以在控制臺輸出中找到:

輸入正確的用戶名和密碼后點擊Login按鈕即被重新302到http://127.0.0.1:8080/getData并顯示查詢數(shù)據(jù):

這表示我們的接口已經(jīng)被spring保護了。

那么肯定會有小伙伴吐槽了,這么復(fù)雜的密碼,鬼才及得住,所以...

二、為Spring Security設(shè)定用戶名和密碼

為了解決復(fù)雜密碼的問題,我們可以在application.yml中做如下設(shè)定:

spring:
 security:
  user:
   name: user
   password: 123

這樣我們就可以通過用戶名user密碼123來訪問http://127.0.0.1:8080/getData接口了。

然后肯定又有小伙伴吐槽了,整個系統(tǒng)就一個用戶么?有哪個系統(tǒng)是只有一個用戶的?所以...

三、為Spring Security設(shè)定多個用戶

如果想要給Spring Security設(shè)定多個用戶可用,則新建一個class,實現(xiàn)接口WebMvcConfigurer(注意:springBoot版本2.0以上,jdk1.8以上):

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

  @Bean
  public UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("admin").password("admin").roles("").build());
    manager.createUser(User.withUsername("guest").password("guest").roles("").build());
    return manager;
  }

}

注意需要注解@EnableWebSecurity

InMemoryUserDetailsManager:顧名思義,將用戶名密碼存儲在內(nèi)存中的用戶管理器。我們通過這個管理器增加了兩個用戶,分別是:用戶名admin密碼admin,用戶名guest密碼guest。

做完如上更改后重啟應(yīng)用,再次訪問http://127.0.0.1:8080/getData,輸入admin/admin或guest/guest即可通過身份驗證并正常使用接口了。

看到這肯定又有小伙伴要吐槽了:用戶數(shù)據(jù)直接硬編碼到代碼里是什么鬼!我要把用戶放在數(shù)據(jù)庫!所以...

四、SpringSecurity+Mysql

想要使用數(shù)據(jù)庫,那么我們可以

1. 增加如下依賴:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

2. 配置數(shù)據(jù)庫連接

spring:
 datasource:
  driver-class-name: com.mysql.jdbc.Driver
  url: jdbc:mysql://192.168.2.12:3306/test?characterEncoding=utf8
  username: root
  password: onceas

3. 創(chuàng)建測試用表結(jié)構(gòu)及數(shù)據(jù)

drop table if exists test.user;
create table test.user (
 id int auto_increment primary key,
 username varchar(50),
 password varchar(50)
);

insert into test.user(id, username, password) values (1, 'admin', 'admin');
insert into test.user(id, username, password) values (2, 'guest', 'guest');

我們創(chuàng)建了用戶信息表,并插入兩個用戶信息,用戶名/密碼依然是admin/admin、guest/guest

4. entity、dao、service

public class User {

  private int id;
  private String username;
  private String password;

  // get set ...
}
@Repository
public class LoginDao {

  private final JdbcTemplate jdbcTemplate;

  @Autowired
  public LoginDao(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }

  public List<User> getUserByUsername(String username) {
    String sql = "select id, username, password from user where username = ?";
    return jdbcTemplate.query(sql, new String[]{username}, new BeanPropertyRowMapper<>(User.class));
  }
}
@Service
public class LoginService {

  private final LoginDao loginDao;

  @Autowired
  public LoginService(LoginDao loginDao) {
    this.loginDao = loginDao;
  }

  public List<User> getUserByUsername(String username) {
    return loginDao.getUserByUsername(username);
  }

}

5. 調(diào)整WebSecurityConfig

@Bean
public UserDetailsService userDetailsService() {
  return username -> {
    List<UserEntity> users = loginService.getUserByUsername(username);
    if (users == null || users.size() == 0) {
      throw new UsernameNotFoundException("用戶名未找到");
    }
    String password = users.get(0).getPassword();
    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String passwordAfterEncoder = passwordEncoder.encode(password);
    return User.withUsername(username).password(passwordAfterEncoder).roles("").build();
  };
}

做完如上更改后重啟應(yīng)用,再次訪問http://127.0.0.1:8080/getData,輸入admin/admin或guest/guest即可通過身份驗證并正常使用接口了。

關(guān)于UserDetailsService,有些東西要說明下:

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String passwordAfterEncoder = passwordEncoder.encode(password);

上面這兩句代碼是在對用戶密碼進行加密。為什么要這樣子呢?看到這肯定又有小伙伴會吐槽:數(shù)據(jù)庫存儲銘文密碼是什么鬼!對,Spring也是盡量在幫助開發(fā)者避免這個事情。所以SpringSecurity在進行密碼比對的時候需要開發(fā)者提供加密后的密碼。我們上面的寫法其實是不合理的,實際情況應(yīng)該是數(shù)據(jù)庫中存儲密文密碼,然后將數(shù)據(jù)庫中的密碼直接傳給User.password()就可以了。

6. 關(guān)于SpringSecurity加密后的密文格式

我們可以通過打斷點的方式或者增加

System.out.println(username + "---->>>" + passwordAfterEncoder);

來查看下,如果admin/admin被登錄時候,passwordAfterEncoder的值是什么?輸出結(jié)果:

admin---->>>{bcrypt}$2a$10$d4VkiIfP7MyNSipjLtQ0Keva4ST6U6Fnw77iiv39IGnGswptqWRG.
guest---->>>{bcrypt}$2a$10$8jRMbiGzFIS4GU3SWAm83eWgFO29EEb5QhXOEkPEaabw5Oiy/jxUC

可以看出加密后的密碼可以分為兩部分

{}內(nèi)描述了加密算法,這里為bcrypt算法。{}后面即為密文密碼,這里是包含鹽的。

所以SpringSecurity的工作原理就是:當(dāng)用戶輸入用戶名和密碼點擊Login以后,SpringSecurity先通過調(diào)用我們自定義的UserDetailsService獲取到加密后密碼,然后根據(jù){}里的內(nèi)容獲知加密算法,再將用戶輸入的密碼按照該算法進行加密,最后再與{}后的密文密碼比對即可獲知用戶憑據(jù)是否有效。

通過查看PasswordEncoderFactories的源碼,我們可以知道SpringEncoder工具可以提供哪些加密算法:

public static PasswordEncoder createDelegatingPasswordEncoder() {
    String encodingId = "bcrypt";
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put(encodingId, new BCryptPasswordEncoder());
    encoders.put("ldap", new LdapShaPasswordEncoder());
    encoders.put("MD4", new Md4PasswordEncoder());
    encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
    encoders.put("noop", NoOpPasswordEncoder.getInstance());
    encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
    encoders.put("scrypt", new SCryptPasswordEncoder());
    encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
    encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
    encoders.put("sha256", new StandardPasswordEncoder());
    return new DelegatingPasswordEncoder(encodingId, encoders);
  }

其中LdapShaPasswordEncoder、Md4PasswordEncoder、MessageDigestPasswordEncoder、NoOpPasswordEncoder、StandardPasswordEncoder已經(jīng)不建議使用了。SpringSecurity認為:

Digest based password encoding is not considered secure. //基于摘要的密碼編碼被認為是不安全的

五 、權(quán)限控制

以上內(nèi)容我們只解決了用戶登錄問題,但是實際開發(fā)中僅僅完成用戶登錄是不夠的,我們還需要用戶授權(quán)及授權(quán)驗證。由于我們已經(jīng)將用戶信息存儲到數(shù)據(jù)庫里了,那么姑且我們也將權(quán)限信息存儲在數(shù)據(jù)庫吧。

1. 準(zhǔn)備數(shù)據(jù)庫表及測試數(shù)據(jù)

drop table if exists test.role;
create table test.role (
 id int auto_increment primary key,
 role varchar(50)
);

drop table if exists test.permission;
create table test.permission (
 id int auto_increment primary key,
 permission varchar(50)
);

drop table if exists test.user_r_role;
create table test.user_r_role (
 userid int,
 roleid int
);

drop table if exists test.role_r_permission;
create table test.role_r_permission (
 roleid int,
 permissionid int
);

drop table if exists test.user_r_permission;
create table test.user_r_permission (
 userid int,
 permissionid int
);

insert into test.role(id, role) values (1, 'adminRole');
insert into test.role(id, role) values (2, 'guestRole');

insert into test.permission(id, permission) values (1, 'permission1');
insert into test.permission(id, permission) values (2, 'permission2');
insert into test.permission(id, permission) values (3, 'permission3');
insert into test.permission(id, permission) values (4, 'permission4');

insert into test.user_r_role(userid, roleid) values (1, 1);
insert into test.user_r_role(userid, roleid) values (2, 2);

insert into test.role_r_permission(roleid, permissionid) values (1, 1);
insert into test.role_r_permission(roleid, permissionid) values (1, 2);

insert into test.user_r_permission(userid, permissionid) values (1, 3);
insert into test.user_r_permission(userid, permissionid) values (1, 4);
insert into test.user_r_permission(userid, permissionid) values (2, 3);
insert into test.user_r_permission(userid, permissionid) values (2, 4);
  1. role:角色信息表,permission權(quán)限信息表,user_r_role用戶所屬角色表,role_r_permission角色擁有權(quán)限表,user_r_permission用戶擁有權(quán)限表。
  2. 由于用戶有所屬角色且角色是有權(quán)限的,用戶同時又單獨擁有權(quán)限,所以用戶最終擁有的權(quán)限取并集。
  3. 用戶admin最終擁有角色adminRole以及權(quán)限:permission1、permission2、permission3、permission4
  4. 用戶guest最終擁有角色guestRole以及權(quán)限:permission3、permission4

2. Dao、Service

dao增加方法:根據(jù)用戶名查角色以及根據(jù)用戶名查權(quán)限

public List<String> getPermissionsByUsername(String username) {
  String sql =
      "select d.permission\n" +
      "from user a\n" +
      "    join user_r_role b on a.id = b.userid\n" +
      "    join role_r_permission c on b.roleid = c.roleid\n" +
      "    join permission d on c.permissionid = d.id\n" +
      "where a.username = ?\n" +
      "union\n" +
      "select c.permission\n" +
      "from user a\n" +
      "    join user_r_permission b on a.id = b.userid\n" +
      "    join permission c on b.permissionid = c.id\n" +
      "where a.username = ?";
  return jdbcTemplate.queryForList(sql, new String[]{username, username}, String.class);
}

public List<String> getRoleByUsername(String username) {
  String sql =
      "select c.role\n" +
      "from user a\n" +
      "    join user_r_role b on a.id = b.userid\n" +
      "    join role c on b.roleid = c.id\n" +
      "where a.username = ?";
  return jdbcTemplate.queryForList(sql, new String[]{username}, String.class);
}

service增加方法:根據(jù)用戶名查角色以及根據(jù)用戶名查權(quán)限

public List<String> getPermissionsByUsername(String username) {
  return loginDao.getPermissionsByUsername(username);
}

public List<String> getRoleByUsername(String username) {
  return loginDao.getRoleByUsername(username);
}

3. WebSecurityConfig

(1)調(diào)整public UserDetailsService userDetailsService()方法,在構(gòu)建用戶信息的時候把用戶所屬角色和用戶所擁有的權(quán)限也填充上(最后return的時候)。

@Bean
public UserDetailsService userDetailsService() {
  return username -> {
    List<UserEntity> users = loginService.getUserByUsername(username);
    if (users == null || users.size() == 0) {
      throw new UsernameNotFoundException("用戶名未找到");
    }
    String password = users.get(0).getPassword();
    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String passwordAfterEncoder = passwordEncoder.encode(password);
    System.out.println(username + "/" + passwordAfterEncoder);

    List<String> roles = loginService.getRoleByUsername(username);
    List<String> permissions = loginService.getPermissionsByUsername(username);

    String[] roleArr = new String[roles.size()];
    String[] permissionArr = new String[permissions.size()];

    return User.withUsername(username).password(passwordAfterEncoder).
        roles(roles.toArray(roleArr)).authorities(permissions.toArray(permissionArr)).
        build();
  };
}

這里面有個坑,就是紅色代碼部分。具體可查看org.springframework.security.core.userdetails.User.UserBuilder。roles()方法和authorities()方法實際上都是在針對UserBuilder的authorities屬性進行set操作,執(zhí)行roles("roleName")和執(zhí)行authorities("ROLE_roleName")是等價的。所以上例代碼中roles(roles.toArray(roleArr))起不到任何作用,直接被后面的authorities(permissions.toArray(permissionArr))覆蓋掉了。

所以正確的寫法可參考:

@Bean
public UserDetailsService userDetailsService() {
  return username -> {
    List<UserEntity> users = loginService.getUserByUsername(username);
    if (users == null || users.size() == 0) {
      throw new UsernameNotFoundException("用戶名未找到");
    }
    String password = users.get(0).getPassword();
    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String passwordAfterEncoder = passwordEncoder.encode(password);
    System.out.println(username + "/" + passwordAfterEncoder);

    List<String> roles = loginService.getRoleByUsername(username);
    List<String> permissions = loginService.getPermissionsByUsername(username);

    String[] permissionArr = new String[roles.size() + permissions.size()];
    int permissionArrIndex = 0;
    for (String role : roles) {
      permissionArr[permissionArrIndex] = "ROLE_" + role;
      permissionArrIndex++;
    }
    for (String permission : permissions) {
      permissionArr[permissionArrIndex] = permission;
      permissionArrIndex++;
    }
    return User.withUsername(username).password(passwordAfterEncoder).authorities(permissionArr).build();
  };
}

(2)增加新的bean,為我們需要的保護的接口設(shè)定需要權(quán)限驗證:

@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
  return new WebSecurityConfigurerAdapter() {
    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
      httpSecurity.
          authorizeRequests().antMatchers("/guest/**").permitAll().
          and().authorizeRequests().antMatchers("/admin/**").hasRole("admin").
          and().authorizeRequests().antMatchers("/authenticated/**").authenticated().
          and().authorizeRequests().antMatchers("/permission1/**").hasAuthority("permission1").
          and().authorizeRequests().antMatchers("/permission2/**").hasAuthority("permission2").
          and().authorizeRequests().antMatchers("/permission3/**").hasAuthority("permission3").
          and().authorizeRequests().antMatchers("/permission4/**").hasAuthority("permission4").
          and().formLogin().
          and().authorizeRequests().anyRequest().permitAll();
    }
  };
}
  1. /guest/**的接口會被允許所有人訪問,包括未登錄的人。
  2. /admin/**的接口只能被擁有admin角色的用戶訪問。
  3. /authenticated/**的接口可以被所有已經(jīng)登錄的用戶訪問。
  4. /permission1/**的接口可以被擁有permission1權(quán)限的用戶訪問。/permission2/**、/permission3/**、/permission4/**同理

4. TestController

最后我們調(diào)整下TestContrller,增加幾個接口以便測試:

@RestController
public class TestController {

  @GetMapping("getData")
  public String getData() {
    return "date";
  }

  @GetMapping("authenticated/getData")
  public String getAuthenticatedData() {
    return "authenticatedData";
  }

  @GetMapping("admin/getData")
  public String getAdminData() {
    return "adminData";
  }

  @GetMapping("guest/getData")
  public String getGuestData() {
    return "guestData";
  }

  @GetMapping("permission1/getData")
  public String getPermission1Data() {
    return "permission1Data";
  }

  @GetMapping("permission2/getData")
  public String getPermission2Data() {
    return "permission2Data";
  }

  @GetMapping("permission3/getData")
  public String getPermission3Data() {
    return "permission3Data";
  }

  @GetMapping("permission4/getData")
  public String getPermission4Data() {
    return "permission4Data";
  }

}

5. 測試

  1. 訪問/guest/getData無需登錄即可訪問成功。
  2. 訪問/authenticated/getData,會彈出用戶登錄頁面。登錄任何一個用戶都可訪問成功。
  3. 訪問/admin/getData,會彈出用戶登錄頁面。登錄admin用戶訪問成功,登錄guest用戶會發(fā)生錯誤,403未授權(quán)。
  4. 其他的就不再贅述了。

六、自定義登錄頁面

是不是覺得SpringScurity的登錄頁面丑爆了?是不是想老子還能做一個更丑的登錄頁面你信不信?接下來我們來弄一個更丑的登錄頁面。

1. 增加pom依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2. 編寫自己的登錄頁面

thymeleaf默認的頁面放置位置為:classpath:templates/ 目錄下,所以在編寫代碼的時候我們可以將頁面放在resources/templates目錄下,名稱為:login.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>一個更丑的登錄頁面</title>
</head>
<body>
  <form method="post" action="/login">
    用戶名:<input name="username" placeholder="請輸入用戶名" type="text">
    密碼:<input name="password" placeholder="請輸入密碼" type="password">
    <input value="登錄" type="submit">
  </form>
</body>
</html>

3. 將SpringSecurity指向自定義的登錄頁面

(1)調(diào)整WebSecurityConfig注入的WebSecurityConfigurerAdapter,在and().formLogin()后面增加loginPage("/login")以指定登錄頁面的uri地址,同時關(guān)閉csrf安全保護。

@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
  return new WebSecurityConfigurerAdapter() {
    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
      httpSecurity.
          authorizeRequests().antMatchers("/guest/**").permitAll().
          ...省略部分代碼...
          and().formLogin().loginPage("/login").
          and().authorizeRequests().anyRequest().permitAll().
          and().csrf().disable();
    }
  };
}

(2)TestController增加login方法(注意我們之前在TestController類上注解了@RestController,這里要記得改成@Controller,否則訪問/login的時候會直接返回字符串而不是返回html頁面。另外除了下面新增的/login方法其他方法要增加注解@ResponseBody)

@GetMapping("login")
public String login() {
  return "login";
}

4. 測試及其他

測試過程就略吧。還有一些要囑咐的東西給小白們:

  • 我們通過loginPage("/login")來告知SpringSecurity自定義登錄頁面的uri路徑,同時這個設(shè)定也告知了用戶點擊登錄按鈕的時候form表單post的uri路徑。即:如果SpringSecurity判定需要用戶登錄,會將302到/login (get請求),用戶輸入用戶名和密碼點擊登錄按鈕后,也需要我們自定義頁面post到/login才能讓SpringSecurity完成用戶認證過程。
  • 關(guān)于html中輸入用戶名的input的name屬性值本例為username、輸入密碼的input的name屬性值本例為password,這是因為SpringSecurity在接收用戶登錄請求時候默認的參數(shù)名就是username和password、如果想更改這兩個參數(shù)名,可以這樣設(shè)定:and().formLogin().loginPage("/login").usernameParameter("username").passwordParameter("password")
  • 測試過程中我們可以試著輸錯用戶名和密碼點擊登錄,會發(fā)現(xiàn)頁面又重新跳轉(zhuǎn)到 http://127.0.0.1:8080/login?error ,只不過后面增加了參數(shù)error且沒有參數(shù)值。所以需要我們再login.html中處理相應(yīng)的邏輯。當(dāng)然你也可以指定用戶認證失敗時候的跳轉(zhuǎn)地址,可以這樣設(shè)定:and().formLogin().loginPage("/login").failureForwardUrl("/login/error")
  • 測試過程中,如果我們直接訪問http://127.0.0.1:8080/login,輸入正確的用戶名和密碼后跳轉(zhuǎn)到http://127.0.0.1:8080即網(wǎng)站根目錄。如果你想指定用戶登錄成功后的默認跳轉(zhuǎn)地址,可以這樣設(shè)定:and().formLogin().loginPage("/login").successForwardUrl("/login/success")

七、登出

登出呢?有登錄了,怎么能沒有登出呢?其實SpringSecurity已經(jīng)早早的為我們默認了一個登出功能,你訪問:http://127.0.0.1:8080/logout 試試看?

如果想做我們自己的個性化登出,可以繼續(xù)調(diào)整WebSecurityConfig注入的WebSecurityConfigurerAdapter

@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
  return new WebSecurityConfigurerAdapter() {
    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
      httpSecurity.
          authorizeRequests().antMatchers("/guest/**").permitAll().
          and().authorizeRequests().antMatchers("/admin/**").hasRole("admin").
          and().authorizeRequests().antMatchers("/authenticated/**").authenticated().
          and().authorizeRequests().antMatchers("/permission1/**").hasAuthority("permission1").
          and().authorizeRequests().antMatchers("/permission2/**").hasAuthority("permission2").
          and().authorizeRequests().antMatchers("/permission3/**").hasAuthority("permission3").
          and().authorizeRequests().antMatchers("/permission4/**").hasAuthority("permission4").
          and().formLogin().loginPage("/login").
          and().logout().logoutUrl("/logout").logoutSuccessUrl("/logoutSuccess").
                  invalidateHttpSession(true).deleteCookies("cookiename").
                  addLogoutHandler(new MyLogoutHandle()).logoutSuccessHandler(new MyLogoutSuccessHandle()).
          and().authorizeRequests().anyRequest().permitAll().
          and().csrf().disable();
    }
  };
}

MyLogoutHandle實現(xiàn)了LogoutHandler接口:

public class MyLogoutHandle implements LogoutHandler {

  @Override
  public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
    System.out.println("==================>>>> LogoutHandler Begin");
    System.out.println(authentication.getPrincipal());
    System.out.println("==================>>>> LogoutHandler End");
  }
}

•MyLogoutSuccessHandle實現(xiàn)了LogoutSuccessHandler接口:

public class MyLogoutSuccessHandle implements LogoutSuccessHandler {

  @Override
  public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
    System.out.println("==================>>>> LogoutSuccessHandler Begin");
    System.out.println(authentication.getPrincipal());
    System.out.println("==================>>>> LogoutSuccessHandler End");
  }
}
  • logoutUrl():告訴SpringSecurity用戶登出的接口uri地址是什么
  • logoutSuccessUrl():告訴SpringSecurity完成用戶登出后要跳轉(zhuǎn)到哪個地址。如果設(shè)定了LogoutSuccessHandler則logoutSuccessUrl設(shè)定無效
  • invalidateHttpSession:執(zhí)行登出的同時是否清空session
  • deleteCookies:執(zhí)行登出的同時刪除那些cookie
  • addLogoutHandler:執(zhí)行登出的同時執(zhí)行那些代碼

八、SpringSecurity在Restfull中的變通使用

當(dāng)前環(huán)境前后盾分離已經(jīng)是大趨勢了吧,除非那些很小很小的項目。所以SpringBoot項目更多的時候為前端提供接口,而并不提供前端頁面路由的功能。所以,當(dāng)SpringSecurity在Restfull開發(fā)中還需要變通一下:

1.首先我們通過and().formLogin().loginPage("/login")設(shè)定的跳轉(zhuǎn)到登錄頁面的GET請求不再指向html,而是直接返回json數(shù)據(jù)告知前端需要用戶登錄。
2.用戶執(zhí)行登錄的時候,前端執(zhí)行post請求到/login進行用戶身份校驗。
3.然后我們通過and().formLogin().failureForwardUrl("/login/error")和and().formLogin().successForwardUrl("/login/error")設(shè)定的登錄成功和失敗跳轉(zhuǎn)來地址來返回json數(shù)據(jù)給前端告知其用戶認證結(jié)果。
4.最后我們通過and().logout().logoutSuccessHandler(new MyLogoutSuccessHandle())來返回json數(shù)據(jù)給前端告知用戶已經(jīng)完成登出。

九、SpringSecurity+SpringSession+Redis

接下來還有一個問題要處理。在上面的案例中,session都是存儲在servlet容器中的,如果我們需要多點部署負載均衡的話,就會出現(xiàn)問題。比如:我們部署了兩個服務(wù)并做了負載均衡,用戶登錄時調(diào)用其中一臺服務(wù)進行身份認證通過并將用戶登錄信息存儲在了這臺服務(wù)器的session里,接下來用戶訪問其他接口,由于負載均衡的存在用戶請求被分配到了另一個服務(wù)上,該服務(wù)檢測用戶session不存在啊,于是就拒絕訪問。

在SpringBoot環(huán)境下解決這個問題也很簡答,很容易就想到SpringSession。所以我們嘗試用SpringSession+Redis解決此問題

1. 增加pom依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>

2. 修改application.yml

spring:
 redis:
  host: 192.168.2.12
  port: 6379
  password: 123456

 session:
  store-type: redis

3. 修改主啟動類,增加@EnableRedisHttpSession注解,開啟SpringSession

十、通過注解的方式實現(xiàn)權(quán)限控制

首先要在主啟動類上增加@EnableGlobalMethodSecurity注解,具體參數(shù)如下:

1.@EnableGlobalMethodSecurity(securedEnabled=true)

支持@Secured注解,例如

@Secured("ROLE_adminRole")

2.@EnableGlobalMethodSecurity(jsr250Enabled=true)

支持@RolesAllowed、@DenyAll、@PermitAll 注解,例如:

@RolesAllowed("ROLE_guestRole")

3.@EnableGlobalMethodSecurity(prePostEnabled=true)

支持@PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter注解,它們使用SpEL能夠在方法調(diào)用上實現(xiàn)更有意思的安全性約束

  • @PreAuthorize :在方法調(diào)用之前,基于表達式的計算結(jié)果來限制對方法的訪問,只有表達式計算結(jié)果為true才允許執(zhí)行方法
  • @PostAuthorize 在方法調(diào)用之后,允許方法調(diào)用,但是如果表達式計算結(jié)果為false,將拋出一個安全性異常
  • @PostFilter 允許方法調(diào)用,但必須按照表達式來過濾方法的結(jié)果
  • @PreFilter 允許方法調(diào)用,但必須在進入方法之前過濾輸入值

由于這里涉及到SpEL表達式,所以本文就不詳細說了。

十一、在Controller中獲取當(dāng)前登錄用戶

public String getAuthenticatedData(HttpSession session) {
    //SecurityContext securityContext = SecurityContextHolder.getContext();
    SecurityContext securityContext = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
    // 以上獲取securityContext的兩種方法二選一
    WebAuthenticationDetails userDetailsService = (WebAuthenticationDetails) securityContext.getAuthentication().getDetails();
    UserDetails userDetails = (UserDetails) securityContext.getAuthentication().getPrincipal();

    System.out.println("===userDetailsService.getRemoteAddress()===>>" + userDetailsService.getRemoteAddress());
    System.out.println("===userDetailsService.getSessionId()===>>" + userDetailsService.getSessionId());
    System.out.println("===userDetails.getRemoteAddress()===>>" + userDetails.getUsername());
    System.out.println("===userDetails.getPassword()===>>" + userDetails.getPassword());
    System.out.println("===userDetails.getAuthorities()===>>" + userDetails.getAuthorities());
    return "authenticatedData";
  }

十二、總結(jié)

SpringSecurity的使用基本就上面這些。就業(yè)務(wù)邏輯來說,SpringSecurity中所謂的role概念嚴(yán)格意義并不能稱之為“角色”。理由是:如果我們的權(quán)限控制比較簡單,整個系統(tǒng)中的角色以及角色所擁有的權(quán)限是固定的,那么我們可以將SpringSecurity的role概念拿來即用。但是如果我們的權(quán)限控制是可配置,用戶和角色是多對多關(guān)系、角色和權(quán)限也是多對多關(guān)系,那么我們只能講SpringSecurity的role當(dāng)做“權(quán)限”來使用。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 關(guān)于Mybatis 中使用Mysql存儲過程的方法

    關(guān)于Mybatis 中使用Mysql存儲過程的方法

    這篇文章給大家介紹了Mybatis 中使用Mysql存儲過程的方法,本文通過實例代碼相結(jié)合的形式給大家介紹的非常詳細,具有參考借鑒價值,需要的朋友參考下吧
    2018-03-03
  • Spring計時器StopWatch的具體使用

    Spring計時器StopWatch的具體使用

    本文主要介紹了Spring計時器StopWatch的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • SpringBoot解決跨域問題小結(jié)

    SpringBoot解決跨域問題小結(jié)

    在現(xiàn)代Web應(yīng)用中,由于安全性和隱私的考慮,瀏覽器限制了從一個域向另一個域發(fā)起的跨域HTTP請求,Spring?Boot提供了多種方式來處理跨域請求,本文將介紹其中的幾種方法,感興趣的朋友一起看看吧
    2023-12-12
  • 基于Springboot商品進銷存管理系統(tǒng)的設(shè)計與實現(xiàn)

    基于Springboot商品進銷存管理系統(tǒng)的設(shè)計與實現(xiàn)

    本項目基于springboot實現(xiàn)一個進銷存管理系統(tǒng),主要用戶開設(shè)網(wǎng)店的相關(guān)商品的進貨、銷售、庫存的管理,功能比較完整,需要的可以參考一下
    2022-08-08
  • 2020年編程選Java的8大理由,JAVA前景如何

    2020年編程選Java的8大理由,JAVA前景如何

    這篇文章主要介紹了2020年編程選Java的8大理由,JAVA前景如何,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • SpringBoot項目部署在weblogic中間件的注意事項說明

    SpringBoot項目部署在weblogic中間件的注意事項說明

    這篇文章主要介紹了SpringBoot項目部署在weblogic中間件的注意事項說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • java方法重寫(重點講),方法重載問題

    java方法重寫(重點講),方法重載問題

    這篇文章主要介紹了java方法重寫(重點講),方法重載問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • 淺談Java內(nèi)存區(qū)域與對象創(chuàng)建過程

    淺談Java內(nèi)存區(qū)域與對象創(chuàng)建過程

    下面小編就為大家?guī)硪黄獪\談Java內(nèi)存區(qū)域與對象創(chuàng)建過程。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • Springboot獲取文件內(nèi)容如何將MultipartFile轉(zhuǎn)File

    Springboot獲取文件內(nèi)容如何將MultipartFile轉(zhuǎn)File

    本文給大家介紹Springboot獲取文件內(nèi)容,將MultipartFile轉(zhuǎn)File方法,本文結(jié)合示例代碼給大家介紹的非常詳細,感興趣的朋友一起看看吧
    2024-01-01
  • Java springboot Mongodb增刪改查代碼實例

    Java springboot Mongodb增刪改查代碼實例

    這篇文章主要介紹了Java springboot Mongodb增刪改查代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-07-07

最新評論