Spring Security【三】web权限方案

视频链接:SpringSecurity框架教程
文章源码:https://github.com/geyiwei-suzhou/spring-security

用户认证(设置用户名、密码)

设置登录的用户名和密码

  • 第一种方式:通过配置文件
    修改application.properties,添加如下内容:
    spring.security.user.name=antherd
    spring.security.user.password=antherd
    
  • 第二种方式:配置类
    package com.antherd.securitydemo.config;
    
    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.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("lucy").password(password).roles("admin");
      }
    
      // PasswordEncoder: 配置密码加密类,否则运行报错 There is no PasswordEncoder mapped for the id "null"
      @Bean
      PasswordEncoder password() {
        return new BCryptPasswordEncoder();
      }
    }
    
  • 第三种方式:自定义编写实现类
  1. 创建配置类,设置使用哪个userDetailService实现类

    package com.antherd.securitydemo.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 SecurityConfigTest extends WebSecurityConfigurerAdapter {
      
      @Autowired
      private UserDetailsService userDetailsService;
    
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
      }
    
      // PasswordEncoder: 配置密码加密类,否则运行报错 There is no PasswordEncoder mapped for the id "null"
      @Bean
      PasswordEncoder password() {
        return new BCryptPasswordEncoder();
      }
    }
    
  2. 编写实现类,返回User对象,User对象有用户名密码和操作权限

    package com.antherd.securitydemo.service;
    
    import com.antherd.securitydemo.entity.Users;
    import com.antherd.securitydemo.mapper.UsersMapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import java.util.List;
    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;
    
    @Service("userDetailsService")
    public class MyUserDetailsService implements UserDetailsService {
    
      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> auths = 
            AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
        return new User("lucy", new BCryptPasswordEncoder().encode(123), auths);
      }
    }
    

启动项目,访问:http://localhost:8111/test/hello,自动跳转到登陆页面,输入配置的用户名密码后,跳转到访问页面。

用户认证(查询数据库完成认证)

整合MyBatisPlus完成数据库操作

  1. 引入相关依赖

    <!-- mybatis-plus -->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.0.5</version>
    </dependency>
    
    <!-- mysql -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
    <!-- lombok 用来简化实体类 -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
    
  2. 创建数据库demo和数据库表

    CREATE TABLE `users` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(100) DEFAULT NULL,
      `password` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    添加数据

    INSERT INTO `demo`.`users` (`id`, `username`, `password`) VALUES (1, 'lucy', '123');
    INSERT INTO `demo`.`users` (`id`, `username`, `password`) VALUES (2, 'mary', '456');
    
  3. 创建users表对应实体类

    package com.antherd.securitydemo.entity;
    
    import lombok.Data;
    
    @Data
    public class Users {
    
      private Integer id;
    
      private String username;
    
      private String password;
    }
    
  4. 整合MybatisPlus,创建接口,继承map的接口

    package com.antherd.securitydemo.mapper;
    
    import com.antherd.securitydemo.entity.Users;
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface UsersMapper extends BaseMapper<Users> {
    
    }
    
  5. 在UserDetailsService调用mapper里面的方法查询数据库进行用户认证

    package com.antherd.securitydemo.service;
    
    import com.antherd.securitydemo.entity.Users;
    import com.antherd.securitydemo.mapper.UsersMapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import java.util.List;
    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;
    
    @Service("userDetailsService")
    public class MyUserDetailsService implements UserDetailsService {
    
      @Autowired
      private UsersMapper usersMapper;
    
      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 调用usersMapper方法,根据用户名查询数据库
        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        // where username = ?
        wrapper.eq("username", username);
        Users users = usersMapper.selectOne(wrapper);
        // 判断
        if (users == null) { // 数据库没有用户名, 认证失败
          throw new UsernameNotFoundException("用户名不存在!");
        }
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        // 从查询数据库返回users对象,得到用户名和密码,返回
        return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
      }
    }
    
  6. 在启动类添加注解MapperScan

    @MapperScan("com.antherd.securitydemo.mapper")
    
  7. 配置文件中配置数据库信息

    # mysql数据库连接
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=123456
    
  8. 启动测试

用户认证(自定义用户登录页面)
  1. 在配置类实现相关的配置

    package com.antherd.securitydemo.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 SecurityConfigTest extends WebSecurityConfigurerAdapter {
    
      @Autowired
      private UserDetailsService userDetailsService;
    
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
      }
    
      // PasswordEncoder: 配置密码加密类,否则运行报错 There is no PasswordEncoder mapped for the id "null"
      @Bean
      PasswordEncoder password() {
        return new BCryptPasswordEncoder();
      }
    
      @Override
      protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() // 自定义自己编写的登录页面
            .loginPage("/login.html") // 登录页面设置
            .loginProcessingUrl("/user/login") //登录访问路径
            .defaultSuccessUrl("/test/index").permitAll() // 登录成功后,跳转路径
            .and().authorizeRequests() //
              .antMatchers("/", "/test/hello", "/user/login").permitAll() // 设置哪些路径可以直接访问,不需要认证
            .anyRequest().authenticated()
            .and().csrf().disable(); // 关闭csrf防护
    
      }
    }
    
  2. 创建相关页面,controller
    在resource文件夹下创建static文件夹,添加文件login.html,用户名、密码参数必须为:username、password

    <!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="text" name="password">
        <br/>
        <input type="submit" value="login" />
      </form>
    </body>
    </html>
    

    在TestController中添加接口

    @GetMapping("/index")
    public String index() {
      return "hello index";
    }
    
  3. 启动测试
    访问:http://localhost:8111/test/hello 不需要认证
    访问:http://localhost:8111/test/index 跳转到自定义认证页面

用户认证(基于权限访问控制)
  • hasAuthority方法:如果当前的主体有指定的权限,则返回true,否则返回false
  1. 在配置类设置当前访问地址有哪些权限

    // 当前登录用户,只有具有admins权限才可以访问这个路径
    .antMatchers("/test/index").hasAuthority("admins")
    
  2. 在UserDetailsService,把返回User对象设置权限

    List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins"); // 将admins改成123,返回登录访问403
    
  • hasAnyAuthority方法:如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true

    .antMatchers("/test/index").hasAnyAuthority("admins, manager")
    
  • hasRole方法:如果用户具备给定角色就允许访问,否则出现403。如果当前主体具有指定的角色,则返回true
    查看源码

    return "hasRole('ROLE_" + role + "')";
    

    MyUserDetailsService

    AuthorityUtils.commaSeparatedStringToAuthorityList("admins, ROLE_sale"); // 角色权限需要加上"ROLE_"前缀
    

    SecurityConfigTest

    .antMatchers("/test/index").hasRole("sale")
    
  • hasAnyRole方法:表示用户具备任何一个条件都可以访问
    MyUserDetailsService

    AuthorityUtils.commaSeparatedStringToAuthorityList("admins, ROLE_admin, ROLE_sale");
    

    SecurityConifgTest

    .antMatchers("/test/index").hasAnyRole("sale")
    
用户认证(自定义403页面)

MyUserDetailsService

AuthorityUtils.commaSeparatedStringToAuthorityList("admins, ROLE_admin, ROLE_sale1`");

403
在resource/static中新建页面 unauth.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>title</title>
</head>
<body>
  <h1>没有访问权限!</h1>
</body>
</html>

在配置类中配置即可,SecurityConfigTest.configure,添加如下内容:

// 配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
用户授权(注解使用)
  • Secured:判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀"ROLE_"
  1. 启动类(配置类)开启注解
    @EnableGlobalMethodSecurity(securedEnabled = true)
    
  2. 在controller的方法上面使用注解,设置角色
    @GetMapping("/update")
    @Secured({"ROLE_sale", "ROLE_manager"})
    public String update() {
    	return "hello update";
    }
    
  3. userDetailService设置用户角色
    AuthorityUtils.commaSeparatedStringToAuthorityList("admins, ROLE_admin, ROLE_sale");
    
  • PreAuthorize:注解适合进入方法前的权限校验,可以将登陆用户的roles/permissions参数传到方法中
  1. 启动类(配置类)开启注解
    @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
    
  2. 在controller的方法上面使用注解,设置角色
    @GetMapping("/update")
    @PreAuthorize("hasAnyAuthority('admins')")
    public String update() {
      return "hello update";
    }
    
  3. userDetailService设置用户角色
    AuthorityUtils.commaSeparatedStringToAuthorityList("admins, ROLE_admin, ROLE_sale");
    
  • PostAuthority:注解使用并不多,在方法执行后再进行权限校验,适合验证带有返回值的权限
    使用此注解先要在启动类上开启注解功能
  1. 启动类(配置类)开启注解
    @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
    
  2. 在controller的方法上面使用注解,设置角色
    @GetMapping("/update")
    @PreAuthorize("hasAnyAuthority('admins')")
    public String update() {
      return "hello update";
    }
    
  3. userDetailService设置用户角色,admin,controller中权限为admins
    AuthorityUtils.commaSeparatedStringToAuthorityList("admin, ROLE_admin, ROLE_sale");
    
  4. 访问:http://localhost:8111/test/update ,返回403页面,但是方法执行了
  • PreFilter:传入方法数据进行过滤

  • PostFilter:方法返回数据进行过滤

    AuthorityUtils.commaSeparatedStringToAuthorityList("admins, ROLE_admin, ROLE_sale");
    
    @GetMapping("/getAll")
    @PostAuthorize("hasAnyAuthority('admins')")
    @PostFilter("filterObject.username=='admin1'")
    public List<Users> getAllUser() {
      ArrayList<Users> list = new ArrayList<>();
      list.add(new Users(11, "admin1", "6666"));
      list.add(new Users(21, "admin2", "8888"));
      System.out.println(list);
      return list;
    }
    

访问:http://localhost:8111/test/getAll ,看一下返回值

用户注销
  1. 在配置类中添加退出的配置

    // 退出
    http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
    
  2. 测试:
    ① 修改配置类,登录成功之后跳转到成功页面

    .defaultSuccessUrl("/success.html").permitAll() // 登录成功后,跳转路径
    

    ② 在成功页面添加超链接,写设置的退出路径

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

    ③ 登录成功后,在点击页面点击退出

    访问:http://localhost:8111/login.html、然后新开标签页访问 http://localhost:8111/login.html,可以访问,在点击退出按钮,在访问 http://localhost:8111/login.html,发现不能访问

自动登录

安全框架机制实现自动登录

一、实现原理
自动登录原理
二、具体实现

  1. 创建数据库表(可以自动生成)
    JdbcTokenRepositoryImpl.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)
    
  2. 配置类,注入数据源,配置操作数据库对象

    // 注入数据源
    @Autowired
    private DataSource dataSource;
    
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
      JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
      jdbcTokenRepository.setDataSource(dataSource);
      // jdbcTokenRepository.setCreateTableOnStartup(true); // 自动创建表
      return jdbcTokenRepository;
    }
    
  3. 配置类中配置自动登录

    .and().rememberMe().tokenRepository(persistentTokenRepository())
    .tokenValiditySeconds(60) // 设置有效时长,单位秒
    .userDetailsService(userDetailsService)
    
  4. 在登录页面中添加复选框

    <input type="checkbox" name="remember-me" />自动登录
    <br />
    
  5. 访问 http://localhost:8111/login.html
    autoLogin
    浏览器中存入了cookies,并且数据库中记录了相关信息
    cookies
    数据库数据
    关闭浏览器后,重启打开,访问 http://localhost:8111/login.html 发现登录任然有效

CSRF

跨站请求伪造(英语:Cross-site request forgery)
从Spring Security 4.0 开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用,Spring Security CSRF会针对PATCH、POST、PUT 和 DELETE 方法进行防护

存储token
在pom文件中引入如下依赖

<!-- 对Thymeleaf添加Spring Security标签支持 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

在templates目录下新建三个页面
csrf/csrf_token.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>用户修改</title>
</head>
<body>
  <div>
    <span th:text="${_csrf.token}"></span>
  </div>
</body>
</html>

csrf/csrfTest.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>用户修改</title>
</head>
<body>
  <div align="center">
    <form method="post" action="update_token">
      <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
      用户名:<input type="text" name="username" /> <br />&nbsp;&nbsp;码:<input type="password" name="password" /> <br />
      <button type="submit">修改</button>
    </form>
  </div>
</body>
</html>

login/login.html

<!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="text" name="password">
  <br/>
  <input type="checkbox" name="remember-me" />自动登录
  <br />
  <input type="submit" value="login" />
</form>
</body>
</html>

新建 CSRFUserDetailsService

package com.antherd.securitydemo.service;

import java.util.ArrayList;
import java.util.List;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service("userDetailsService")
public class CSRFUserDetailsService implements UserDetailsService {

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

    List<SimpleGrantedAuthority> list = new ArrayList<>();
    list.add(new SimpleGrantedAuthority("role"));
    UserDetails userDetails = new User("lucy", new BCryptPasswordEncoder().encode("123"),
        list);
    return userDetails;
  }
}

SecurityConfigCsrf

package com.antherd.securitydemo.config;

import org.springframework.beans.factory.annotation.Autowired;
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;

@Configuration
public class SecurityConfigCsrf extends WebSecurityConfigurerAdapter {

  @Autowired
  private UserDetailsService userDetailsService;

  // 实现用户身份认证
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // 配置url的访问权限
    http.authorizeRequests()
        .antMatchers("/").permitAll()
        .antMatchers("/**update**").permitAll()
        .antMatchers("/login/**").permitAll()
        .anyRequest().authenticated();

    // 关闭csrf保护功能
//    http.csrf().disable();

    // 使用自定义的登录窗口
    http.formLogin()
        .loginPage("/userLogin").permitAll()
        .usernameParameter("username").passwordParameter("password")
        .defaultSuccessUrl("/")
        .failureUrl("/userLogin?error");
  }
}

CSRFController

package com.antherd.securitydemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class CSRFController {

  @GetMapping("/toupdate")
  public String test(Model model) {
      return "csrf/csrfTest";
  }

  @PostMapping("/update_token")
  public String getToken() {
    return "csrf/csrf_token";
  }
}

LoginController

package com.antherd.securitydemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {

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

现将 csrfTest页面中如下内容注释掉

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">

启动测试,访问 http://localhost:8111/toupdate , 发现登录触发CSRF,跳转到 /userLogin?error 页面
取消上面注释后,在启动测试,可以正常登录

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值