注意,在配置过滤器时 每个权限/角色配置操作前面都要写上请求路径
如: .anyRequest() 请求路径
.authenticated()//都需要认证操作 是权限/角色操作
三种用户认证方式(设置用户名密码的)
如果打开crsf防护需要在页面中加入下面代码
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
加这个的目的是每次请求时都把csrf防护生成的随机token带上
第一种:通过配置文件(非查库)
应用添加security依赖后,在application配置文件中写入spring.security.user.name和spring.security.user.password即可,若注入成功 控制台不会报security框架自动生成的初始密码
# 应用名称
spring.application.name=spring-security-smalldemo
# 应用服务 WEB 访问端口
server.port=8080
#设置用户名密码方式一 配置文件
spring.security.user.name=lql
spring.security.user.password=mima
第二种 通过配置类(非查库)
在配置类中编写如下即可
package com.li.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;
/** 设置用户名密码的第二种方式 配置类
* @author liql
* @date 2021/9/21
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//密码需要加密 不然报 Encoded password does not look like BCrypt 错误
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123456");
//角色不能不设置,不然报错
auth.inMemoryAuthentication().withUser("lql").password(password).roles("");
}
//不注入PasswordEncoder 会报下面的错误
//java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
第三种 通过实现 UserDetailsService接口设置用户名密码(可查库)
分别定义一个配置类和接口实例类即可,
用户名密码查库操作可定义在该实例中只需要替换返回对象User中的参数为数据库查到的即可。
配置类
package com.li.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;
/** 设置用户名密码的第三种方式 通过实现 userDetailsService 接口
* @author liql
* @date 2021/9/21
*/
@Configuration
public class SecurityConfig2 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
//不注入PasswordEncoder 会报下面的错误
//java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
用户名密码写死
package com.li.config;
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;
import java.util.List;
/**
* 配置第三种用户密码的方式的实现类 UserDetailsService接口是security框架的
* @author liql
* @date 2021/9/21
*/
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//设置权限
List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList("roles");
//第三个参数为设置权限,不能为空。
return new User("lql", new BCryptPasswordEncoder().encode("123"), authorityList);
}
}
通过查库获取用户名对应的密码
参数 username为登录界面输入的用户名,把查库拿到的密码传入到返回值中,security底层会自动将查库的密码与登录界面输入的密码做校验
package com.li.config;
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;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* 配置第三种用户密码的方式的实现类 UserDetailsService接口是security框架的
* @author liql
* @date 2021/9/21
*/
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
//定义一个安全类集合模拟数据库
private static ConcurrentHashMap<String,String> mydb=new ConcurrentHashMap();
static {
mydb.put("lql", "111");
mydb.put("xiaohong", "123");
}
/**
*
* @param username 该参数为登录界面输入的用户名
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//设置权限
List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList("roles");
//模拟通过数据库查密码
String password=mydb.get(username);
// if(查库没查到){
// throw new UsernameNotFoundException("用户名不存在")
// }
//把数据库查到的密码传入进去 security框架底层会帮我们自动做校验 第三个参数为设置权限,不能为空。
return new User(username, new BCryptPasswordEncoder().encode(password), authorityList);
}
}
自定义设置登录页面
在原先的配置类中重写 void: configure(HttpSecurity http) 方法
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
重写后
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login.html") //登录界面
.loginProcessingUrl("/user/login") //用户密码需要提交到的路径 该路径由security管理 不需要我们定义
.defaultSuccessUrl("/test/index")//登录成功后跳转到的页面
.permitAll()// 无条件允许访问 不校验权限
.and()
.authorizeRequests()//后面写认证配置
.anyRequest() //任何请求
.authenticated()//都需要认证操作
// .antMatchers("/","/test/hello").permitAll() //设置哪些无条件允许访问 但必须先认证然后不校验权限
.and()
.csrf().disable();//关闭csrf防护 关闭跨域保护
}
添加依赖和配置
spring.thymeleaf.prefix=classpath:/static
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
login.html
页面 username password 参数名是固定的,因为在UsernamePasswordAuthenticationFilter过滤器中明确定义了
<!DOCTYPE html>
<!-- 需要添加
<html xmlns:th="http://www.thymeleaf.org">
这样在后面的th标签就不会报错
-->
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>xx</title>
</head>
<body>
<h1>表单提交</h1>
<!-- 表单提交用户信息,注意字段的设置,直接是*{} -->
<form action="/user/login" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<input type="text" name="username" />
<input type="text" name="password" />
<input type="submit" />
</form>
</body>
</html>
登录成功跳转页面
@GetMapping("/index")
@ResponseBody
public String index(){
return "登录成功";
}
基于权限进行控制
给用户授权
在UserDetailsService的实例类里进行授权
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//设置权限
List<GrantedAuthority> authorityList =
AuthorityUtils
//该方法 commaSeparatedStringToAuthorityList 技能设置权限又能设置角色,设置角色时加前缀 ROLE_XXX , 多个权限和角色中间用逗号隔开
.commaSeparatedStringToAuthorityList("perm_hello1,perm_hello2");
//模拟通过数据库查密码
String password=mydb.get(username);
// if(查库没查到){
// throw new UsernameNotFoundException("用户名不存在")
// }
//把数据库查到的密码传入进去 security框架底层会帮我们自动做校验 第三个参数为设置权限和角色,不能为空。
return new User(username, new BCryptPasswordEncoder().encode(password), authorityList);
}
设置访问路径的权限
在配置类里
@Override
protected void configure(HttpSecurity http) throws Exception {
/* http.formLogin().loginPage("/login.html") //登录界面
.loginProcessingUrl("/user/login") //用户密码需要提交到的路径 该路径由security管理 不需要我们定义
.defaultSuccessUrl("/test/index")//登录成功后跳转到的页面
.permitAll()// 无条件允许访问 但必须先认证然后不校验权限
.and()
.authorizeRequests()//后面写认证配置
.anyRequest() //任何请求
.authenticated()//都需要认证操作
// .antMatchers("/","/test/hello").permitAll() //设置哪些路径可以不用认证就能访问
.and()
.csrf().disable();//关闭csrf防护 关闭跨域保护*/
http.formLogin().loginPage("/login.html") //登录界面
.loginProcessingUrl("/user/login") //用户密码需要提交到的路径 该路径由security管理 不需要我们定义
.defaultSuccessUrl("/test/index")//登录成功后跳转到的页面
.permitAll()// 无条件允许访问 但必须先认证然后不校验权限
.and()
.authorizeRequests()
.antMatchers("/test/hello")
.hasAnyAuthority("perm_hello")//满足该权限 ,只能写单个权限
.antMatchers("/test/hello2")
//满足下面权限的一个即可放行
.hasAnyAuthority("perm_hello,perm_hello2")
.anyRequest() //任何请求
.authenticated()//都需要认证操作
.and()
.csrf().disable();//关闭csrf防护 关闭跨域保护;
}
基于角色访问
给用户设置角色
与设置权限的地方相同 ,在实例类中 设置权限的地方增加 ROLE_xxx 即可
加“ROLE_”前缀的原因是,在配置过滤器时 如写个角色为 “admin” 但是在底层会校验“ROLE_admin”;
List<GrantedAuthority> authorityList =
AuthorityUtils
//该方法 commaSeparatedStringToAuthorityList 技能设置权限又能设置角色,设置角色时加前缀 ROLE_XXX , 多个权限和角色中间用逗号隔开
//ROLE_admin 给用户设置 admin角色
.commaSeparatedStringToAuthorityList("perm_hello1,perm_hello2,ROLE_admin");
给路径设置角色
与设置路径的地方相同 在配置类中
增加
.antMatchers("/test/hello2")
//满足下面角色的一个即可放行
.hasRole("admin")
.antMatchers("/test/hello3")
//满足其中的一个角色即可放行
.hasAnyRole("admin,admin2")
@Override
protected void configure(HttpSecurity http) throws Exception {
/* http.formLogin().loginPage("/login.html") //登录界面
.loginProcessingUrl("/user/login") //用户密码需要提交到的路径 该路径由security管理 不需要我们定义
.defaultSuccessUrl("/test/index")//登录成功后跳转到的页面
.permitAll()// 无条件允许访问 但必须先认证然后不校验权限
.and()
.authorizeRequests()//后面写认证配置
.anyRequest() //任何请求
.authenticated()//都需要认证操作
// .antMatchers("/","/test/hello").permitAll() //设置哪些路径可以不用认证就能访问
.and()
.csrf().disable();//关闭csrf防护 关闭跨域保护*/
http.formLogin().loginPage("/login.html") //登录界面
.loginProcessingUrl("/user/login") //用户密码需要提交到的路径 该路径由security管理 不需要我们定义
.defaultSuccessUrl("/test/index")//登录成功后跳转到的页面
.permitAll()// 无条件允许访问 但必须先认证然后不校验权限
.and()
.authorizeRequests()
.antMatchers("/test/hello")
.hasAnyAuthority("perm_hello")//满足该权限 ,只能写单个权限
.antMatchers("/test/hello2")
//满足下面权限的一个即可放行
.hasAnyAuthority("perm_hello,perm_hello2")
.antMatchers("/test/hello2")
//满足下面角色的一个即可放行
.hasRole("admin")
.antMatchers("/test/hello3")
//满足其中的一个角色即可放行
.hasAnyRole("admin,admin2")
.anyRequest() //任何请求
.authenticated()//都需要认证操作
.and()
.csrf().disable();//关闭csrf防护 关闭跨域保护;
}
自定义无权限时的返回页面
依然在配置过滤器的方法中加入 即可 页面
//设置无权限时返回的页面
http.exceptionHandling().accessDeniedPage("/test/unauthen");
@GetMapping("/unauthen")
@ResponseBody
public String unauthen(){
return "无权访问";
}
退出登录
在配置类中加上如下配置即可
//配置退出
http.logout()
.logoutUrl("/logout") //输入该url表示退出
.logoutSuccessUrl("/test/logout")//成功退出后跳转的页面
.permitAll();
基于注解配置
自动登录
自动登录的实现原理是:
- 浏览器请求登录
- 验证通过 给浏览器返回一个标记自动登录 token,并同时把这个token存入到数据库
- 下次登录 / 访问资源的时候,浏览器携带token进行请求,框架会拿到这个token与数据库存的token做对比,相同则放行,否则重新登录。
开发步骤
- 配置类注入数据源
//注入数据源 使用rememberme-me 时需要
@Autowired
private DataSource dataSource;
- 配置类中创建操作数据库的对象
//配置操作数据库的对象
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//下面这个api需要导入 orm框架相关的架包 如 mybatisplus
jdbcTokenRepository.setDataSource(dataSource);
//下面这个api可以帮我们自动在数据库创建 rememberme-me 时需要的数据库表一般第一次使用的时候打开
//如果表已经存在会报错
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
- 在配置类过滤器中配置自动登录的配置
.and().
rememberMe().
tokenRepository(persistentTokenRepository())//引入操作数据库的类
.tokenValiditySeconds(600)//设置有效时长,单位秒
.userDetailsService(userDetailsService)
- 页面要配置 name=“remember_me”的输入框
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>xx</title>
</head>
<body>
<h1>表单提交</h1>
<form action="/user/login" method="post">
<input type="text" name="username" />
<input type="text" name="password" />
<input type="checkbox"name="remember-me"title="记住密码"/>记住密码<br/>
<input type="submit" />
</form>
</body>
</html>
完整的配置类
package com.li.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;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
/** 设置用户名密码的第三种方式 通过实现 userDetailsService 接口
* @author liql
* @date 2021/9/21
*/
@Configuration
public class SecurityConfig2 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
//不注入PasswordEncoder 会报下面的错误
//java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
/* http.formLogin().loginPage("/login.html") //登录界面
.loginProcessingUrl("/user/login") //用户密码需要提交到的路径 该路径由security管理 不需要我们定义
.defaultSuccessUrl("/test/index")//登录成功后跳转到的页面
.permitAll()// 无条件允许访问 但必须先认证然后不校验权限
.and()
.authorizeRequests()//后面写认证配置
.anyRequest() //任何请求
.authenticated()//都需要认证操作
// .antMatchers("/","/test/hello").permitAll() //设置哪些路径可以不用认证就能访问
.and()
.csrf().disable();//关闭csrf防护 关闭跨域保护*/
http.formLogin().loginPage("/login.html") //登录界面
.loginProcessingUrl("/user/login") //用户密码需要提交到的路径 该路径由security管理 不需要我们定义
.defaultSuccessUrl("/test/index")//登录成功后跳转到的页面
.permitAll()// 无条件允许访问 但必须先认证然后不校验权限
.and()
.authorizeRequests()
.antMatchers("/test/hello")
.hasAnyAuthority("perm_hello")//满足该权限 ,只能写单个权限
.antMatchers("/test/hello2")
//满足下面权限的一个即可放行
.hasAnyAuthority("perm_hello,perm_hello2")
.antMatchers("/test/hello2")
//满足下面角色即可放行
.hasRole("admin")
.antMatchers("/test/hello3")
//满足其中的一个角色即可放行
.hasAnyRole("admin,admin2")
.anyRequest() //任何请求
.authenticated()//都需要认证操作
//下面是开启记住我功能的配置
.and().
rememberMe().
tokenRepository(persistentTokenRepository())//引入操作数据库的类
.tokenValiditySeconds(600)//设置有效时长,单位秒
.userDetailsService(userDetailsService)
.and()
.csrf().disable();//关闭csrf防护 关闭跨域保护;
//设置无权限时返回的页面
http.exceptionHandling().accessDeniedPage("/test/unauthen");
//配置退出
http.logout()
.logoutUrl("/logout") //输入该url表示退出
.logoutSuccessUrl("/test/logout")//成功退出后跳转的页面
.permitAll();
}
//注入数据源 使用rememberme-me 时需要,要引入mysql驱动
@Autowired
private DataSource dataSource;
//配置操作数据库的对象
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//下面这个api需要导入 orm框架相关的架包 如 mybatisplus
jdbcTokenRepository.setDataSource(dataSource);
//下面这个api可以帮我们自动在数据库创建 rememberme-me 时需要的数据库表一般第一次使用的时候打开
//如果表已经存在会报错
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}