Spring Security

框架解决问题

本质上是过滤器链,使用这些过滤器解决:
1、用户认证
检验用户是否为系统中的合法主体。

2、用户授权
检验某位用户是否有权限执行某项操作。

使用

一般会搭配SpringBoot使用

依赖

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

指定版本:

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

初步使用

写一个简单的controller,直接返回String 就行
这时候去登录,在前端网页会出现要登录的页面
Spring Security默认用户user,默认生成随机密码在控制台

自定义开发

用户名+密码

自定义类继承UsernamePasswordAuthenticationFilter过滤器,重写attemptAuthentication、successfulAuthentication、unsuccessfulAuthentication方法。
在attemptAuthentication方法中做校验,成功则调用successfulAuthentication方法、失败则调用unsuccessfulAuthentication方法。

注意 做认证差数据库的操作,需要写在实现UserDetailsService接口的类中

步骤

1、创建类继承UsernamePasswordAuthenticationFilter,重写三个方法。
2、创建类实现UserDetailsService接口,编写查询数据过程,返回User对象(这里的User对象是安全框架的)

当返回的User对象密码数据需要加密的时候:使用PasswordEncoder接口:
new一个BCryptPasswordEncoder,使用它的encode方法完成加密。
还需要在@Configuration+@Bean注册一个BCryptPasswordEncoder实例对象进Spring容器

示例:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private UserDetailsService userDetailsService;

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

	@Bean
	PasswordEncoder password(){return new BCryptPasswordEncoder();}
}
@Service
public class LoginService implements UserDetailsService {
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// 判断用户名是否存在
		if (!"admin".equals(username)){
			throw new UsernameNotFoundException("用户名不存在!");
		}
		// 从数据库中获取的密码 atguigu 的密文
		String pwd ="$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na"; 
		return new User(username,pwd, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,"));// 第三个参数表示权限
	}
}

示例

从数据库中查用户名和密码,来完成登录校验

依赖:SpringBoot、Spring Security、MyBatisPlus、MySQL、lombok

配置文件application.properties

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

实体类entity

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

登录实现类Service

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
	@Autowired
	private UsersMapper usersMapper;
	@Override
	public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
		QueryWrapper<Users> wrapper = new QueryWrapper(); 
		wrapper.eq("username",s);
		Users users = usersMapper.selectOne(wrapper);
		if(users == null) {
			throw new UsernameNotFoundException("用户名不存在!");
		}
		System.out.println(users);
		List<GrantedAuthority> auths =AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale"); 
		return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
	}
}

配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private UserDetailsService userDetailsService;

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

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

配置需要认证的路径/登录页面

配置类修改为如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private UserDetailsService userDetailsService;

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

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

	//设置登录页面转到自定义页面
	@Override
	protected void configure(HttpSecurity http)throws Exception{
		http.formLogin() //自定义自己编写的登录页面
			.loginPage("/login.html") //登录页面(当访问需要认证的页面时,会跳转到这个页面)
			.loginProcessingUrl("/user/login") //登录表单路径,就是登录操作触发哪个controller,不需要自己实现,随便写一个
			.defaultSuccessUrl("/success").permitAll() //登录成功之后转到的路径
			.and().authorizeRequests()
				.antMatchers("/","/test/hello","/user/login").permitAll()//访问这些路径不需要认证
				.anyRequest().authenticated()
			.and().csrf().desable();//关闭csrf防护
	}
}

用户授权

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

hasAuthority方法

当前主体有权限时返回true 无权限返回false

配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private UserDetailsService userDetailsService;

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

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

	//设置登录页面转到自定义页面
	@Override
	protected void configure(HttpSecurity http)throws Exception{
		http.formLogin() //自定义自己编写的登录页面
			.loginPage("/login.html") //登录页面(当访问需要认证的页面时,会跳转到这个页面)
			.loginProcessingUrl("/user/login") //登录表单路径,就是登录操作触发哪个controller,不需要自己实现,随便写一个
			.defaultSuccessUrl("/success").permitAll() //登录成功之后转到的路径
			.and().authorizeRequests()
				.antMatchers("/","/test/hello","/user/login").permitAll()//访问这些路径不需要认证
				.andMatchers("/test/index").hasAuthority("admin")//用户需要带有admin权限
				.anyRequest().authenticated()
			.and().csrf().desable();//关闭csrf防护
	}
}

hasAnyAuthority方法

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

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private UserDetailsService userDetailsService;

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

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

	//设置登录页面转到自定义页面
	@Override
	protected void configure(HttpSecurity http)throws Exception{
		http.formLogin() //自定义自己编写的登录页面
			.loginPage("/login.html") //登录页面(当访问需要认证的页面时,会跳转到这个页面)
			.loginProcessingUrl("/user/login") //登录表单路径,就是登录操作触发哪个controller,不需要自己实现,随便写一个
			.defaultSuccessUrl("/success").permitAll() //登录成功之后转到的路径
			.and().authorizeRequests()
				.antMatchers("/","/test/hello","/user/login").permitAll()//访问这些路径不需要认证
				.andMatchers("/test/index").hasAnyAuthority("admin,manager")//用户需要带有admin权限
				.anyRequest().authenticated()
			.and().csrf().desable();//关闭csrf防护
	}
}

hasRole方法

如果用户具备给定角色就允许访问,否则出现403。类似hasAuthority方法。
对应部分改为:.andMatchers("/test/index").hasRole("admin")//用户需要带有admin权限

注意不同点在于,userDetailService返回的角色需要ROLE_xxx(有前缀),而hasRole方法参数则为xxx。

hasAnyRole方法

类似hasAnyAuthority方法。

配置无权限时跳转页面

配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private UserDetailsService userDetailsService;

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

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

	//设置登录页面转到自定义页面
	@Override
	protected void configure(HttpSecurity http)throws Exception{
		http.exceptionHandling().accessDeniedPage("/unauth.html");//设置无权限访问跳转自定义页面
		http.formLogin() //自定义自己编写的登录页面
			.loginPage("/login.html") //登录页面(当访问需要认证的页面时,会跳转到这个页面)
			.loginProcessingUrl("/user/login") //登录表单路径,就是登录操作触发哪个controller,不需要自己实现,随便写一个
			.defaultSuccessUrl("/success").permitAll() //登录成功之后转到的路径
			.and().authorizeRequests()
				.antMatchers("/","/test/hello","/user/login").permitAll()//访问这些路径不需要认证
				.andMatchers("/test/index").hasAnyAuthority("admin,manager")//用户需要带有admin权限
				.anyRequest().authenticated()
			.and().csrf().desable();//关闭csrf防护
	}
}

用户授权(注解使用)主要

@Secured

判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。 使用注解先要开启注解功能,在 启动类或配置类 上添加@EnableGlobalMethodSecurity(securedEnabled=true)注解

在controller层的方法上面加上@Secured注解

@RequestMapping("testSecured")
@ResponseBody 
@Secured({"ROLE_normal","ROLE_admin"}) //需要ROLE_前缀开头
public String helloUser() {
	return "hello,user";
}

@PreAuthorize

注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用户的 roles/permissions 参数传到方法中。使用注解先要开启注解功能,在 启动类或配置类 添加@EnableGlobalMethodSecurity(prePostEnabled = true)

在controller层的方法上面加上@PreAuthorize注解

@RequestMapping("/preAuthorize") @ResponseBody
//@PreAuthorize("hasRole('ROLE_管理员')")
@PreAuthorize("hasAnyAuthority('menu:system')")//注意引号,hasAnyAuthority这里也可以使用另外三个
public String preAuthorize(){
	System.out.println("preAuthorize"); return "preAuthorize";
}

@PostAuthorize

注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值 的权限。启动类开启@EnableGlobalMethodSecurity(prePostEnabled = true)

在controller层的方法上面加上@PostAuthorize注解

@RequestMapping("/testPostAuthorize") 
@ResponseBody @PostAuthorize("hasAnyAuthority('menu:system')") 
public String preAuthorize(){
	System.out.println("test--PostAuthorize"); return "PostAuthorize";
}

@PostFilter

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

controller层方法

@RequestMapping("getAll") 
@PreAuthorize("hasRole('ROLE_管理员')") 
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<UserInfo> getAllUser(){
	ArrayList<UserInfo> list = new ArrayList<>(); 
	list.add(new UserInfo(1l,"admin1","6666")); 
	list.add(new UserInfo(2l,"admin2","888"));
	return list;
}

@PreFilter

进入控制器之前对前端传入数据进行过滤

controller层方法

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

用户注销

用户登录后如何进行退出操作,需要在配置类中添加退出映射地址http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll()

配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	@Autowired
	private UserDetailsService userDetailsService;

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

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

	//设置登录页面转到自定义页面
	@Override
	protected void configure(HttpSecurity http)throws Exception{
		//设置退出
		http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();
		
		http.exceptionHandling().accessDeniedPage("/unauth.html");//设置无权限访问跳转自定义页面
		http.formLogin() //自定义自己编写的登录页面
			.loginPage("/login.html") //登录页面(当访问需要认证的页面时,会跳转到这个页面)
			.loginProcessingUrl("/user/login") //登录表单路径,就是登录操作触发哪个controller,不需要自己实现,随便写一个
			.defaultSuccessUrl("/success").permitAll() //登录成功之后转到的路径
			.and().authorizeRequests()
				.antMatchers("/","/test/hello","/user/login").permitAll()//访问这些路径不需要认证
				.andMatchers("/test/index").hasAnyAuthority("admin,manager")//用户需要带有admin权限
				.anyRequest().authenticated()
			.and().csrf().desable();//关闭csrf防护
	}
}

自动登录

实现效果:需要认证才能访问的页面,在关闭浏览器再次打开后还能不能录直接访问。

原理:把cookie和对应用户信息存入数据库

自动登录

功能实现

1、数据库建表:用户名、密码、token

2、修改配置类:注入数据源、配置操作数据库对象

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	//注入数据源
	@Autowired
	private DataSourse dataSourse;

	//操作数据库的对象
	@Bean
	public PersistentTokenRepository persistentTokenRepository(){
		JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl=new JdbcTokenRepositoryImpl();
		jdbcTokenRepositoryImpl.setDataSourse(dataSourse);
		return jdbcTokenRepositoryImpl;
	}

	@Autowired
	private UserDetailsService userDetailsService;

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

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

	//设置登录页面转到自定义页面
	@Override
	protected void configure(HttpSecurity http)throws Exception{
		//设置退出
		http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();
		
		http.exceptionHandling().accessDeniedPage("/unauth.html");//设置无权限访问跳转自定义页面
		http.formLogin() //自定义自己编写的登录页面
			.loginPage("/login.html") //登录页面(当访问需要认证的页面时,会跳转到这个页面)
			.loginProcessingUrl("/user/login") //登录表单路径,就是登录操作触发哪个controller,不需要自己实现,随便写一个
			.defaultSuccessUrl("/success").permitAll() //登录成功之后转到的路径
			.and().authorizeRequests()
				.antMatchers("/","/test/hello","/user/login").permitAll()//访问这些路径不需要认证
				.andMatchers("/test/index").hasAnyAuthority("admin,manager")//用户需要带有admin权限
				.anyRequest().authenticated()
				.and().rememberMe().tokenRepository(persistentTokenRepository())//设置自动登录
				.tokenValiditySeconds(60)//设置有效期
				.userDetialService(userDetailsService);//查询数据的
			.and().csrf().desable();//关闭csrf防护
	}
}

3、修改配置类configure()方法
注意在“设置自动登录”处

重点

单点登录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值