您好,欢迎访问代理记账网站
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

Spring Security之web权限方案

1. 认证

1.1 方式一

通过配置文件进行设置认证
application.properties

spring.security.user.name=root
spring.security.user.password=123456

1.2 方式二

通过配置类进行设置认证
在config文件夹下创建一个securityConfig.java
SecurityConfig.java

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123456");
        auth.inMemoryAuthentication().withUser("admin").password(password).roles("admin");
    }

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

1.3 方式三

通过自定义编写实现类进行设置认证
第一步创建配置类,设置使用哪个userDetailsService实现类
SecurityConfig2.java

@Configuration
public class SecurityConfig2 extends WebSecurityConfigurerAdapter {

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

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

第二步编写实现类,返回User对象,User对象有用户名密码和操作权限

@Service("userDetailsService")
public class UserDetailServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> roles = AuthorityUtils.commaSeparatedStringToAuthorityList("roles");
        return new User("qwe123",new BCryptPasswordEncoder().encode("123456"),roles);

    }
}

authorities不允许为Null
在这里插入图片描述

1.4 实现数据库来认证用户登录

1.4.1 准备数据库

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

INSERT INTO users VALUES(1,'张san','123');
INSERT INTO users VALUES(2,'李si','456');

1.4.2 搭建项目

在这里插入图片描述

1.4.3 导入依赖

pom.xml

<dependencies>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <!--mybatis-plus-->
   <dependency>
       <groupId>com.baomidou</groupId>
       <artifactId>mybatis-plus-boot-starter</artifactId>
       <version>3.2.0</version>
   </dependency>
   <!--spring security-->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
   </dependency>
   <!--mysql驱动-->
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
   </dependency>
   <!--lombok-->
   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.springframework.security</groupId>
       <artifactId>spring-security-test</artifactId>
       <scope>test</scope>
   </dependency>
</dependencies>

<build>
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
       </plugin>
   </plugins>
</build>

1.4.4 编写实体类

User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}

1.4.5 编写Mapper文件

UsersMapper.java

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

1.4.6 编写配置文件

application.properties

# 配置数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456

1.4.7 开启Mapper扫描

Springboot05SpringSecurityApplication.java

@SpringBootApplication
@MapperScan("com.test.mapper")
public class Springboot05SpringSecurityApplication {

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

}

1.4.8 编写security配置文件

SecurityConfig.java

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

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

1.4.9 编写实现类

UserDetailServiceImpl.java

@Service("userDetailsService")
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        Users user = usersMapper.selectOne(wrapper);
        if (user == null) {
            throw new UsernameNotFoundException("认证失败!");
        }
        List<GrantedAuthority> roles = AuthorityUtils.commaSeparatedStringToAuthorityList("roles");
        return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),roles);
    }
}

1.4.10 编写controller层

TestController.java

@RestController
public class TestController {

    @RequestMapping("/test")
    public String test() {
        return "ok";
    }
}

1.4.11 启动启动类进行测试

在浏览器输入:http://localhost:8080/test

  1. 输入错误的账号和密码
    在这里插入图片描述
  2. 输入正确的账号和密码
    在这里插入图片描述

1.5 自定义登录页面

1.5.1 导入依赖

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

1.5.2 自定义页面配置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //自定义登录页面
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()//自定义登录页面
            .loginPage("/login.html")//登录页面设置
            .loginProcessingUrl("/user/login")//登录页面的访问路径
            .defaultSuccessUrl("/test").permitAll()//登录成功的访问路径
            .and().authorizeRequests()
                  .antMatchers("/","/test","/user/login").permitAll()
            .anyRequest().authenticated()
            .and().csrf().disable();//关闭csrf防护
    }
}

1.5.3 编写login.html页面

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <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>

注意:页面提交方式必须为 post 请求,所以上面的页面不能使用,用户名,密码必须为username,password
原因:
在执行登录的时候会走一个过滤器 UsernamePasswordAuthenticationFilter
在这里插入图片描述
如果修改配置可以调用 usernameParameter()和 passwordParameter()方法。

<form action="/login"method="post">
	用户名:<input type="text"name="loginAcct"/><br/>
	密码:<input type="password"name="userPswd"/><br/>
	<input type="submit"value="提交"/>
</form>

在这里插入图片描述

1.5.4 启动启动类进行测试

在浏览器输入:http://localhost:8080/index
在这里插入图片描述

在这里插入图片描述

2. 授权

2.1 基于权限进行访问控制

2.1.2 hasAuthority 方法

如果当前的主体具有指定的权限,则返回 true,否则返回 false

  • 修改配置类
    在这里插入图片描述
  • 测试:
    在这里插入图片描述
  • 在UserDetailService中的将限修改为admins
    在这里插入图片描述
  • 测试:
    在这里插入图片描述
  • 缺点:对多个权限不能设置同一个路径

2.1.2 hasAnyAuthority 方法

如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true.

  • 修改配置类
    在这里插入图片描述
  • 在UserDetailService中的将限修改为admins或两个都可以,只要其中一个权限就可以访问
    在这里插入图片描述
  • 测试:
    在这里插入图片描述

2.2 基于角色进行访问控制

2.2.1 hasRole 方法

如果用户具备给定角色就允许访问,否则出现 403。
如果当前主体具有指定的角色,则返回 true

底层源码:
在这里插入图片描述

  • 修改配置
    在这里插入图片描述
    查看源码可知,我们给用户赋角色时记得在前面加ROLE_
    在这里插入图片描述
  • 测试:
    在这里插入图片描述

2.2.2 hasAnyRole方法

表示用户具备任何一个条件都可以访问。

  • 修改配置类
    在这里插入图片描述
  • 给用户添加角色:只要其中一个角色就可以访问
    在这里插入图片描述

2.3 自定义403页面

默认403页面
在这里插入图片描述

  • 修改配置类

在这里插入图片描述

  • 添加控制器
@RequestMapping("/to403")
public String to403() {
    return "view/403";
}
  • 添加403.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>403</title>
</head>
<body>
    <h1>403页面</h1>
</body>
</html>
  • 测试
    在这里插入图片描述

2.4 注解使用

2.4.1 @Secured

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

  1. 使用注解先要开启注解功能!
@SpringBootApplication
@MapperScan("com.test.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true)
public class Springboot05SpringSecurityApplication {

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

}
  1. 在控制器方法上添加注解
@RequestMapping("/update")
    @ResponseBody
    @Secured({"ROLE_user","ROLE_menu"})
    public String update() {
        return "hello update";
    }
  1. 添加用户角色
@Service("userDetailsService")
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        Users user = usersMapper.selectOne(wrapper);
        if (user == null) {
            throw new UsernameNotFoundException("认证失败!");
        }
        List<GrantedAuthority> roles = AuthorityUtils.
                commaSeparatedStringToAuthorityList("admins,manager,ROLE_user,ROLE_menu");
        return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),roles);
    }
}
  1. 测试
    在这里插入图片描述

2.4.2 @PreAuthorize

@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用户的 roles/permissions 参数传到方法中。

  1. 先开启注解功能:
@SpringBootApplication
@MapperScan("com.test.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class Springboot05SpringSecurityApplication {

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

}
  1. 在控制器方法上添加注解
@RequestMapping("/update")
	@ResponseBody
	//@Secured({"ROLE_user","ROLE_menu"})
	@PreAuthorize("hasAnyAuthority('admins')")
	public String update() {
	    return "hello update";
}
  1. 添加用户角色
@Service("userDetailsService")
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        Users user = usersMapper.selectOne(wrapper);
        if (user == null) {
            throw new UsernameNotFoundException("认证失败!");
        }
        List<GrantedAuthority> roles = AuthorityUtils.
                commaSeparatedStringToAuthorityList("admins,manager,ROLE_user,ROLE_menu");
        return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),roles);
    }
}
  1. 测试
    在这里插入图片描述

2.4.3 @PostAuthorize

@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.

  1. 先开启注解功能
@SpringBootApplication
@MapperScan("com.test.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class Springboot05SpringSecurityApplication {

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

}
  1. 在控制器方法上添加注解
@RequestMapping("/update")
@ResponseBody
//@Secured({"ROLE_user","ROLE_menu"})
//@PreAuthorize("hasAnyAuthority('admins')")
@PostAuthorize("hasAnyAuthority('admins')")
public String update() {
    return "hello update";
}
  1. 添加用户角色
@Service("userDetailsService")
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        Users user = usersMapper.selectOne(wrapper);
        if (user == null) {
            throw new UsernameNotFoundException("认证失败!");
        }
        List<GrantedAuthority> roles = AuthorityUtils.
                commaSeparatedStringToAuthorityList("admins,manager,ROLE_user,ROLE_menu");
        return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),roles);
    }
}
  1. 测试
    在这里插入图片描述

2.4.4 @PostFilter

@PostFilter :权限验证之后对数据进行过滤
留下用户名是 admin1 的数据表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素

  1. 在控制器方法上添加注解
@RequestMapping("getAll")
@PostAuthorize("hasAnyAuthority('admins')")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<Users> getAllUser(){
    ArrayList<Users> list = new ArrayList<>();
    list.add(new Users(1,"admin1","6666"));
    list.add(new Users(2,"admin2","888"));
    return list;
}
  1. 添加用户角色
@Service("userDetailsService")
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        Users user = usersMapper.selectOne(wrapper);
        if (user == null) {
            throw new UsernameNotFoundException("认证失败!");
        }
        List<GrantedAuthority> roles = AuthorityUtils.
                commaSeparatedStringToAuthorityList("admins,manager,ROLE_user,ROLE_menu");
        return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),roles);
    }
}
  1. 测试

在这里插入图片描述

2.4.5 @PreFilter

@PreFilter: 进入控制器之前对数据(传入的参数)进行过滤

  1. 在控制器方法上添加注解
 @RequestMapping("getTestPreFilter")
    @PostAuthorize("hasAnyAuthority('admins')")
    @PreFilter(value = "filterObject.id%2==0")
    @ResponseBody
    public List<Users> getTestPreFilter(@RequestBody List<Users>
                                                   list){
        list.forEach(t-> {
            System.out.println(t.getId()+"\t"+t.getUsername());
        });
        return list;
    }
  1. 添加用户角色
@Service("userDetailsService")
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        Users user = usersMapper.selectOne(wrapper);
        if (user == null) {
            throw new UsernameNotFoundException("认证失败!");
        }
        List<GrantedAuthority> roles = AuthorityUtils.
                commaSeparatedStringToAuthorityList("admins,manager,ROLE_user,ROLE_menu");
        return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),roles);
    }
}
  1. 测试
    在这里插入图片描述
    在这里插入图片描述
    测试的 Json 数据:
[{
"id": "1",
"username": "admin",
"password": "666"
},{
"id": "2",
"username": "admins",
"password": "888"
},{
"id": "3",
"username": "admins11",
"password": "11888"
},{
"id": "4",
"username": "admins22",
"password": "22888"
}]

3. 用户注销

3.1在配置类中配置

//自定义登录页面
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.logout().logoutUrl("/loginOut").logoutSuccessUrl("/login").permitAll();
    //自定义403
    http.exceptionHandling().accessDeniedPage("/to403");
    http.formLogin()//自定义登录页面
        .loginPage("/login")//登录页面设置
        .loginProcessingUrl("/user/login")//登录页面的访问路径
        .defaultSuccessUrl("/success").permitAll()//登录成功的访问路径
        .and().authorizeRequests()
              .antMatchers("/","/test","/user/login","/login").permitAll()
            //当前登录用户带有admins权限
            //.antMatchers("/index").hasAuthority("admins")
            //.antMatchers("/index").hasAnyAuthority("admins,manager")
            //.antMatchers("/index").hasRole("user")
            //.antMatchers("/index").hasAnyRole("user,menu")
            .anyRequest().authenticated()
        .and().csrf().disable();//关闭csrf防护
}

3.2 编写controller

@RequestMapping("/success")
	public String success() {
	    return "view/success";
}

3.3 编写success.html

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

4. 自动登录

4.1 自动登录实现的方式有

  1. cookie技术
  2. 安全框架机制实现

4.2 原理分析

在这里插入图片描述

4.3 功能实现

4.3.1 编写配置文件

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //tokenRepository.setCreateTableOnStartup(true);//系统内部为我们创建表,第一次执行会创建,以后要执行就要删除掉!
        return tokenRepository;
    }
    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //记住我
        http.rememberMe().tokenRepository(persistentTokenRepository())
            .tokenValiditySeconds(60)//设置有效时长,单位为秒
            .userDetailsService(userDetailsService);
    }
}

4.3.2 编写前端

name为固定值:remember-me
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <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>

4.3.3 测试

登录后按F12可以看到存储remember-me这个cookie
在这里插入图片描述
数据库自动存储数据
在这里插入图片描述

5. CSRF

5.1 什么是CSRF

CSRF:跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的

从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用程序,Spring Security CSRF 会针对 PATCH,POST,PUT 和 DELETE 方法进行防护。

5.2 了解原理

  1. 生成 csrfToken 保存到 HttpSession 或者 Cookie 中。
    在这里插入图片描述
    在这里插入图片描述
  2. SaveOnAccessCsrfToken 类有个接口 CsrfTokenRepository
    在这里插入图片描述
    在这里插入图片描述
  3. 当前接口实现类:HttpSessionCsrfTokenRepository,CookieCsrfTokenRepository
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  4. 请求到来时,从请求中提取 csrfToken和保存的 csrfToken 做比较,进而判断当前请求是否合法。主要通过 CsrfFilter 过滤器来完成。

5.3 简单案例

5.3.1导入依赖

<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

5.3.2 编写配置文件

这句代码是关闭安全配置 csrf,因为默认是开启的,所以测试是需要注释掉,开启。

//http.csrf().disable();//关闭csrf防护

5.3. 3 在登录页面添加隐藏域

没加这句代码即使登录成功也会跳转到失败的页面

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

内容参考:https://www.bilibili.com/video/BV15a411A7kP
仅用于学习!


分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进