计算机系统应用教程网站

网站首页 > 技术文章 正文

10分钟上手SpringSecurity 框架(3)

btikc 2024-09-29 09:57:39 技术文章 18 ℃ 0 评论

SpringSecurity Web 权限方案

(一)设置登录系统的账号、密码

1. 配置文件application.properties

#配置文件配置用户名密码
spring.security.user.name=lisi
spring.security.user.password=123

2. 编写配置类实现

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

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

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

3. 实现UserDetailsService实现

config文件:

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired
   private UserDetailsService userDetailsService;

   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     /* BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
     String pass = encoder.encode("123");
     auth.inMemoryAuthentication().withUser("hello").password(pass).roles("admin");*/
     auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

   }

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

Service文件:

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

4. 实现数据库认证来完成用户登录

(1) 建数据脚本

create table users(
id bigint primary key auto_increment,
username varchar(20) unique not null,
password varchar(100)
);

(2) Pom依赖

<!--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>

(3) 配置文件

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=root

(4) 实体类

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

(5) 整合 MybatisPlus 制作 mapper

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

(6) 登录实现类

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

   @Autowired
   private UserMapper userMapper;

   @Override
   public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
     QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
     queryWrapper.eq("username",s);
     Users users = userMapper.selectOne(queryWrapper);
     if(users == null){
     	throw new UsernameNotFoundException("用户不存在");
     }
     List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
     return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
   }
}

(7) 访问

http://localhost:8080/test/demo1输入同户名密码

5. 自定义登录页面

(1) 编写页面

<body>
   <form action="/user/login" method="post">
     <label>用户名:</label><input type="text" name="username">
     <br>
     <label>密码:</label><input type="password" name="password">
     <br>
     <input type="submit">
   </form>
</body>

(2) 编写配置类

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired
   private UserDetailsService userDetailsService;

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

   @Override
   protected void configure(HttpSecurity http) throws Exception {
     http.formLogin()
     .loginPage("/login.html")//自定义自己编写的登录页面
     .loginProcessingUrl("/user/login") //登录访问路径
     .and().authorizeRequests()
     .antMatchers("/","/user/login","/login.html").permitAll()//设置路径不需要认证可以直接访问
     .anyRequest().authenticated()
     .and().csrf().disable();//关闭csrf防护
   }

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

(3) 其他配置看第4点

(4) 访问


注意:页面提交方式必须为 post 请求,所以上面的页面不能使用,用户名,密码必须为username,password

6. 自定义403错误页面

(1) 编写页面

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

(2) 编写配置类

@Override
 protected void configure(HttpSecurity http) throws Exception {
   //配置无权限访问页面
   http.exceptionHandling().accessDeniedPage("/err.html");

   http.formLogin()
   .loginPage("/login.html")//自定义自己编写的登录页面
   .loginProcessingUrl("/user/login") //登录访问路径
   .and().authorizeRequests()
   .antMatchers("/","/user/login","/login.html").permitAll()//设置路径不需要认证可以直接访问
  // .antMatchers("/test/demo1").hasAuthority("admin")
  // .antMatchers("/test/demo1").hasAnyAuthority("admin,create")
  // .antMatchers("/test/demo1").hasRole("sale")
   .antMatchers("/test/demo1").hasAnyRole("sale1")
   .anyRequest().authenticated()
   .and().csrf().disable();//关闭csrf防护
 }

(3) 访问

http://localhost:8080/test/demo1

(二)基于角色或权限进行访问控制

(1) hasAuthority 方法

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

配置类:

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired
   private UserDetailsService userDetailsService;

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

   }

   @Override
   protected void configure(HttpSecurity http) throws Exception {
     http.formLogin()
     .loginPage("/login.html")//自定义自己编写的登录页面
     .loginProcessingUrl("/user/login") //登录访问路径
     .and().authorizeRequests()
     .antMatchers("/","/user/login","/login.html").permitAll()//设置路径不需要认证可以直接访问
     .antMatchers("/test/demo1").hasAuthority("admin")
     .anyRequest().authenticated()
   	.and().csrf().disable();//关闭csrf防护
   }

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

赋予用户权限:

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

 @Autowired
 private UserMapper userMapper;

 @Override
 public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
 QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
 queryWrapper.eq("username",s);
 Users users = userMapper.selectOne(queryWrapper);
 if(users == null){
 throw new UsernameNotFoundException("用户不存在");
 }
 List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
 return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
 }
}

访问成功:

访问失败:

(2) hasAnyAuthority 方法

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


......
@Override
 protected void configure(HttpSecurity http) throws Exception {
   http.formLogin()
   .loginPage("/login.html")//自定义自己编写的登录页面
   .loginProcessingUrl("/user/login") //登录访问路径
   .and().authorizeRequests()
   .antMatchers("/","/user/login","/login.html").permitAll()//设置路径不需要认证可以直接访问
  // .antMatchers("/test/demo1").hasAuthority("admin")
   .antMatchers("/test/demo1").hasAnyAuthority("admin,create")
   .anyRequest().authenticated()
   .and().csrf().disable();//关闭csrf防护
 }
......


(3) hasRole 方法

如果用户具备给定角色就允许访问,否则出现 403。

如果当前主体具有指定的角色,则返回 true。

底层源码:(底层拼接了ROLE_)

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

给用户添加角色:

配置类:

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired
   private UserDetailsService userDetailsService;

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

   @Override
   protected void configure(HttpSecurity http) throws Exception {
     http.formLogin()
     .loginPage("/login.html")//自定义自己编写的登录页面
     .loginProcessingUrl("/user/login") //登录访问路径
     .and().authorizeRequests()
     .antMatchers("/","/user/login","/login.html").permitAll()//设置路径不需要认证可以直接访问
    // .antMatchers("/test/demo1").hasAuthority("admin")
    // .antMatchers("/test/demo1").hasAnyAuthority("admin,create")
     .antMatchers("/test/demo1").hasRole("sale")
     .anyRequest().authenticated()
     .and().csrf().disable();//关闭csrf防护
   }

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

赋予用户角色:

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

 @Autowired
 private UserMapper userMapper;

 @Override
 public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
 QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
 queryWrapper.eq("username",s);
 Users users = userMapper.selectOne(queryWrapper);
 if(users == null){
 throw new UsernameNotFoundException("用户不存在");
 }
 List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
 return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
 }
}

注意配置文件中不需要添加”ROLE_“,因为上述的底层代码会自动添加与之进行匹配。

(4) hasAnyRole

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

@Override
 protected void configure(HttpSecurity http) throws Exception {
   http.formLogin()
   .loginPage("/login.html")//自定义自己编写的登录页面
   .loginProcessingUrl("/user/login") //登录访问路径
   .and().authorizeRequests()
   .antMatchers("/","/user/login","/login.html").permitAll()//设置路径不需要认证可以直接访问
  // .antMatchers("/test/demo1").hasAuthority("admin")
  // .antMatchers("/test/demo1").hasAnyAuthority("admin,create")
  // .antMatchers("/test/demo1").hasRole("sale")
   .antMatchers("/test/demo1").hasAnyRole("sale,sale1")
   .anyRequest().authenticated()
   .and().csrf().disable();//关闭csrf防护
 }

(三)注解使用

(1) @Secured

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

使用注解先要开启注解功能:EnableGlobalMethodSecurity(securedEnabled=true)

@SpringBootApplication
@MapperScan("com.hk.springsecuritydemo.dao")
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringsecuritydemoApplication {

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

}

控制层:

@RestController
@RequestMapping("/test")
public class TestController {

   @RequestMapping("/demo1")
   public String demo1(){
   	return "hello security";
   }

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

   @RequestMapping("/demo2")
   @Secured({"ROLE_sale2","ROLE_management"})
   public String demo2(){
   	return "demo2";
   }
}

当主体有sale2或者management角色时才有权限访问/demo2接口

例如现在主体有sale角色:

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

   @Autowired
   private UserMapper userMapper;

   @Override
   public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
     QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
     queryWrapper.eq("username",s);
     Users users = userMapper.selectOne(queryWrapper);
     if(users == null){
     	throw new UsernameNotFoundException("用户不存在");
     }
     List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
     return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
   }
}

访问:

http://localhost:8080/test/demo2

(2) @PreAuthorize

先开启注解功能: @EnableGlobalMethodSecurity(prePostEnabled = true)

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

@SpringBootApplication
@MapperScan("com.hk.springsecuritydemo.dao")
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SpringsecuritydemoApplication {

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

}

控制层:

@RequestMapping("/demo3")
@PreAuthorize("hasRole('ROLE_sale')")
public String demo3(){
 	return "demo3";
}

访问:

(3) @PostAuthorize

先开启注解功能: @EnableGlobalMethodSecurity(prePostEnabled = true)

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

@SpringBootApplication
@MapperScan("com.hk.springsecuritydemo.dao")
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SpringsecuritydemoApplication {

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

}

控制层:

@RequestMapping("/demo4")
@PostAuthorize("hasAuthority('admin')")
public String demo4(){
   System.out.println("demo4");
   return "demo4";
}


(4) @PostFilter

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

@RequestMapping("/demo5")
@PostAuthorize("hasAuthority('admin')")
@PostFilter("filterObject.username == 'admin1'")
public ArrayList<Users> demo5(){
   ArrayList<Users> list = new ArrayList<>();
   list.add(new Users(1,"admin1","6666"));
   list.add(new Users(2,"admin2","888"));
   return list;
}

访问:http://localhost:8080/test/demo5

(5) @PreFilter

@PreFilter: 进入控制器之前对数据进行过滤

@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_sale')")
@PreFilter(value = "filterObject.id%2==0")
public List<Users> getTestPreFilter(@RequestBody List<Users> list){
   list.forEach(t-> {
   	System.out.println(t.getId()+"\t"+t.getUsername());
   });
   return list;
}

Postman调用

(四)基于数据库的记住我

(1) 创建表

 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;

(2) 编写配置类

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired
   private UserDetailsService userDetailsService;

   @Autowired
   private DataSource dataSource;

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

   }

   @Override
   protected void configure(HttpSecurity http) throws Exception {
     //配置无权限访问页面
     http.exceptionHandling().accessDeniedPage("/err.html");

     http.formLogin()
     .loginPage("/login.html")//自定义自己编写的登录页面
     .loginProcessingUrl("/user/login") //登录访问路径
     .and().authorizeRequests()
     .antMatchers("/","/user/login","/login.html").permitAll()//设置路径不需要认证可以直接访问
    // .antMatchers("/test/demo1").hasAuthority("admin")
    // .antMatchers("/test/demo1").hasAnyAuthority("admin,create")
    // .antMatchers("/test/demo1").hasRole("sale")
    // .antMatchers("/test/demo1").hasAnyRole("sale")
     .anyRequest().authenticated()
     .and().csrf().disable();//关闭csrf防护

     //配置记住我
     http.rememberMe()
     .rememberMeParameter("remember-me") // 修改请求参数名。 默认是remember-me
     .tokenValiditySeconds(10) // 设置记住我有效时间。单位是秒。默认是14天
     .rememberMeCookieName("remember-me") // 修改remember me的cookie名称。默认是remember-me
     .tokenRepository(persistentTokenRepository()) // 配置用户登录标记的持久化工具对象。
     .userDetailsService(userDetailsService); // 配置自定义的UserDetailsService接口实现类对象
   }

   @Bean
   PersistentTokenRepository persistentTokenRepository(){
     JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
     jdbcTokenRepository.setDataSource(dataSource);
    // jdbcTokenRepository.setCreateTableOnStartup(true);//自动建表
     return jdbcTokenRepository;
   }

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

(3) 修改登录页面

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>login</title>
</head>
<body>
   <form action="/user/login" method="post">
     <label>用户名:</label><input type="text" name="username">
     <br>
     <label>密码:</label><input type="password" name="password">
     <br>
     <input type="checkbox" name="remember-me"> 10秒免登录
     <br>
     <input type="submit">
   </form>
</body>
</html>

(五)用户注销

(1) 在登录成功页面添加一个退出连接

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

(2) 编写配置类

......
@Override
protected void configure(HttpSecurity http) throws Exception {
 //配置无权限访问页面
 http.exceptionHandling().accessDeniedPage("/err.html");

 //配置退出登录
 http.logout().logoutUrl("/logout").logoutSuccessUrl("/login.html");

 http.formLogin()
 .loginPage("/login.html")//自定义自己编写的登录页面
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/success.html").permitAll()//登录成功之后,跳转路径
......

结语:

 每当黎明的太阳升起,美好的一天又开始了

回复SpringSecurity,可以获得全套笔记

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表