SpringSecurity學(xué)習

參考

B站:https://www.bilibili.com/video/BV15a411A7kP

官網(wǎng):https://spring.io/projects/spring-security

簡介

Spring 是非常流行和成功的 Java 應(yīng)用開發(fā)框架,Spring Security 正是 Spring 家族中的 成員。Spring Security 基于 Spring 框架,提供了一套 Web 應(yīng)用安全性的完整解決方 案。

正如你可能知道的關(guān)于安全方面的兩個主要區(qū)域是“認證”和“授權(quán)”(或者訪問控 制),一般來說,Web 應(yīng)用的安全性包括用戶認證(Authentication)和用戶授權(quán) (Authorization)兩個部分,這兩點也是 Spring Security 重要核心功能。

(1)用戶認證指的是:驗證某個用戶是否為系統(tǒng)中的合法主體,也就是說用戶能否訪問 該系統(tǒng)。用戶認證一般要求用戶提供用戶名和密碼。系統(tǒng)通過校驗用戶名和密碼來完成認 證過程。通俗點說就是系統(tǒng)認為用戶是否能登錄

(2)用戶授權(quán)指的是驗證某個用戶是否有權(quán)限執(zhí)行某個操作。在一個系統(tǒng)中,不同用戶 所具有的權(quán)限是不同的。比如對一個文件來說,有的用戶只能進行讀取,而有的用戶可以 進行修改。一般來說,系統(tǒng)會為不同的用戶分配不同的角色,而每個角色則對應(yīng)一系列的 權(quán)限。通俗點講就是系統(tǒng)判斷用戶是否有權(quán)限去做某些事情。

SpringSecurity 特點:

  • 和 Spring 無縫整合。
  • 全面的權(quán)限控制。
  • 專門為 Web 開發(fā)而設(shè)計。
    • 舊版本不能脫離 Web 環(huán)境使用。
    • 新版本對整個框架進行了分層抽取,分成了核心模塊和 Web 模塊。單獨 引入核心模塊就可以脫離 Web 環(huán)境。
  • 重量級。

入門示例

pom.xml

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

Controller測試

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

啟動測試,需要密碼登錄,默認username為user,密碼為控制臺打印的內(nèi)容

啟動流程原理

UserDetailsService

PasswordEncoder

Web

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

方式一

spring:
  security:
    user:
      name: root
      password: root

方式二

@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().withUser("root").password(password).roles("admin");
    }

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

方式三

step1

@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

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

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

step2

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

加入數(shù)據(jù)庫

建立數(shù)據(jù)庫

create table users(
 id bigint primary key auto_increment,
username varchar(20) unique not null,
password varchar(100)
);
-- 密碼 atguigu
insert into users values(1,'張
san','$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na');
-- 密碼 atguigu
insert into users values(2,'李
si','$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na');
create table role(
id bigint primary key auto_increment,
name varchar(20)
);insert into role values(1,'管理員');
insert into role values(2,'普通用戶');
create table role_user(
uid bigint,
rid bigint
);
insert into role_user values(1,1);
insert into role_user values(2,2);
create table menu(
id bigint primary key auto_increment,
name varchar(20),
url varchar(100),
parentid bigint,
permission varchar(20)
);
insert into menu values(1,'系統(tǒng)管理','',0,'menu:system');
insert into menu values(2,'用戶管理','',0,'menu:user');
create table role_menu(
mid bigint,
rid bigint
);
insert into role_menu values(1,1);insert into role_menu values(2,1);
insert into role_menu values(2,2);

pom.xml

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo
    username: root
    password: root

entity

@Data
@AllArgsConstructor
public class Users {
    private Long id;
    private String username;
    private String password;
}

mapper

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

user配置

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

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 調(diào)用usersMapper方法查詢數(shù)據(jù)庫

        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        // where username = ?
        wrapper.eq("username", s);
        Users users = usersMapper.selectOne(wrapper);
        // 判斷
        if (users == null) {
            throw new UsernameNotFoundException("用戶名不存在!");
        }

        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        // 從數(shù)據(jù)庫返回users對象,得到用戶名、密碼,返回
        return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
    }
}

自定義登錄

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定義登陸頁面
        http.formLogin()
                .loginPage("/login.html") // 登錄頁面設(shè)置
                .loginProcessingUrl("/user/login") // 登錄訪問路徑
                .defaultSuccessUrl("/test/index").permitAll() // 登錄成功跳轉(zhuǎn)路徑
                .and().authorizeRequests()
                .antMatchers("/", "/test/hello").permitAll() //設(shè)置哪些路徑可以直接訪問,不需要認證
                .anyRequest().authenticated()
                .and().csrf().disable();
    }

基于角色或權(quán)限進行訪問控制

hasAuthority

hasAnyAuthority

hasRole

hasAnyRole

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定義登陸頁面
        http.formLogin()
                .loginPage("/login.html") // 登錄頁面設(shè)置
                .loginProcessingUrl("/user/login") // 登錄訪問路徑
                .defaultSuccessUrl("/test/index").permitAll() // 登錄成功跳轉(zhuǎn)路徑
                .and().authorizeRequests()
                .antMatchers("/", "/test/hello").permitAll() //設(shè)置哪些路徑可以直接訪問,不需要認證
                // 1.hasAuthority()
                // .antMatchers("/test/index").hasAuthority("admins")
                // 2.hasAnyAuthority()
                // .antMatchers("/test/index").hasAnyAuthority("admins,manager")
                // 3.hasRole() ROLE_
                .antMatchers("/test/index").hasRole("sale")
                // 4.hasAnyRole()
                .antMatchers("/test/index").hasAnyRole("sale,dba")
                .anyRequest().authenticated()
                .and().csrf().disable();
    }
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    // 調(diào)用usersMapper方法查詢數(shù)據(jù)庫

    QueryWrapper<Users> wrapper = new QueryWrapper<>();
    // where username = ?
    wrapper.eq("username", s);
    Users users = usersMapper.selectOne(wrapper);
    // 判斷
    if (users == null) {
        throw new UsernameNotFoundException("用戶名不存在!");
    }

    List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
    // 從數(shù)據(jù)庫返回users對象,得到用戶名、密碼,返回
    return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
}

403頁面

// 配置403頁面
http.exceptionHandling().accessDeniedPage("/unauth.html");

注解

@Secured

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

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

@EnableGlobalMethodSecurity(securedEnabled=true)

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

@PreAuthorize

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

先開啟注解功能:

@EnableGlobalMethodSecurity(prePostEnabled = true)

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

@PostAuthorize

使用并不多,在方法執(zhí)行后再進行權(quán)限驗證,適合驗證帶有返回值 的權(quán)限.

先開啟注解功能:

@EnableGlobalMethodSecurity(prePostEnabled = true)

先執(zhí)行,后驗證權(quán)限,雖無權(quán)限,但還是輸出了

    @GetMapping("/update")
    // @Secured({"ROLE_sale","ROLE_manager"})
    // @PreAuthorize("hasAnyAuthority('admins')")
    @PostAuthorize("hasAnyAuthority('admin')")
    public String update(){
        System.out.println("update...");
        return "hello update";
    }

@PostFilter

權(quán)限驗證之后對數(shù)據(jù)進行過濾 留下用戶名是 admin1 的數(shù)據(jù) 表達式中的 filterObject 引用的是方法返回值 List 中的某一個元素

@PreFilter

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

注銷

// logout
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();

記住我

原理

建表

CREATE TABLE `persistent_logins` (
 `username` varchar(64) NOT NULL,
 `series` varchar(64) NOT NULL,
 `token` varchar(64) NOT NULL,
 `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP,
 PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    @Autowired
    private DataSource dataSource;


    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 可自動建表
        // jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
// 記住我
.and().rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60)
.userDetailsService(userDetailsService)
<form action="/user/login" method="post">
    用戶名:<input type="text" name="username"/><br/>
    密碼:<input type="password" name="password"/><br/>
    <input type="checkbox" name="remember-me"/>自動登錄<br/>
    <input type="submit" value="login"/>
</form>

CSRF

CSRF 理解

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

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

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

默認是開啟的

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

總結(jié)

簡單的學(xué)習,原理部分沒有深究,還有部分不完整,如結(jié)合數(shù)據(jù)庫權(quán)限,當然還有分布式?jīng)]有學(xué)習,目前分布式還有太多前期知識為準備,所以沒有太注重這方面

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容