文章目录
Spring Security 简介
Spring 是非常流行和成功的java开发框架,Spring Security正是Spring 家族中的成员。Spring Security基于Spring框架,提供一套Web应用安全性的解决方案。
正如您可能知道的关于安全方面的两个区域 “认证"和"授权”(或者访问控制),一般来说,Web应用的安全性包括 **用户认证(Authentication) 和 用户授权(Authorization)**两个部分,这两点也是Spring Security重要核心功能。
(1)用户认证指的是: 验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色对应一系列权限。通俗点讲就是系统判断用户是否有权限去做某些事情
入门案例
-
创建一个Springboot工程
-
改pom
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
-
创建一个controller
@RestController @RequestMapping("/login") public class LoginController { @GetMapping("hello") public String hello(){ return "Hello world"; } }
4.测试
Spring Security基本原理
Spring Security 本质是一个过滤器链
FileterSecurityInterceptor:方法级的权限过滤器,基于过滤器的最底部
ExceptionTranslationFilter:判断授权过程中的异常
UsernamePasswordAuthenticationFilter: 校验用户名和密码
Spring Security过滤器是如何加载的?
Spring boot对于Spring Security提供了自动化配置方案:
使用SpringSecurity配置过滤器
DelegatingFilterProxy
UseDetailsService接口
用于查询数据库用户名和密码
创建一个类 继承UsernamePasswordAuthenticationFilter,重写三个方法
创建类 实现UseDetailsService,编写查询数据过程,返回User对象
这个User对象是安全框架提供的对象
PasswordEncoder接口
对密码进行加密
WEB权限方案
如何设置登录的用户名和密码
第一种方式:通过配置application文件进行配置
spring.security.user.name=admin
spring.security.user.password=admin
第二种范式:创建配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String password = encoder.encode("admin");
auth.inMemoryAuthentication().withUser("admin").password(password).roles("admin");
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
第三种方式:自定义编写实现类
登录,首先security框架会去配置文件和配置类中找 用户名和 密码 ,如果不存在,就会去找UserDetailService接口
-
创建配置类,设置使用哪个UserDetailService实现类
@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(); } }
-
编写实现类,返回User对象,User对象有用户名、密码和权限
@Service("userDetailsService") public class MyUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { Collection<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); User user = new User("admin",new BCryptPasswordEncoder().encode("admin"),role); return user; } }
-
测试
基于mybatis plus配置的方式登录
pom
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/boot1?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=034312
entity
@Data
public class User {
private Integer id;
private String name;
private String pwd;
}
mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
}
config
@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();
}
}
service
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService{
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<com.zhj.springsecuritydemo.entity.User> wrapper = new QueryWrapper<>();
wrapper.eq("name",username);
com.zhj.springsecuritydemo.entity.User user = userMapper.selectOne(wrapper);
if(user == null){
throw new UsernameNotFoundException("用户名不存在");
}
return new User(user.getName()
,new BCryptPasswordEncoder().encode(user.getPwd())
,AuthorityUtils.commaSeparatedStringToAuthorityList("role"));
}
}
启动类
@SpringBootApplication
@MapperScan("com.zhj.springsecuritydemo.mapper")
public class SpringsecuritydemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringsecuritydemoApplication.class, args);
}
}
测试
自定义登录界面
config
@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();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 自定义登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/login/index").permitAll() // 登录成功之后跳转路径
.and().authorizeRequests()
.antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
.anyRequest().authenticated()
.and().csrf().disable();// 关闭csrf防护
}
}
controller
@RestController
@RequestMapping("/login")
public class LoginController {
@GetMapping("hello")
public String hello(){
return "Hello world";
}
@GetMapping("index")
public String index(){
return "login success";
}
}
static
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<pre>
<form action="/user/login" method="post">
用户名: <input type="text" name="username"> <!-- name="username" 不允许变-->
密码: <input type="password" name="password"> <!-- name="password" 不允许变-->
<input type="submit" value="登录" >
</form>
</pre>
</body>
</html>
权限设置
hasAuthority :当前主体具有制定的权限,则返回true,否则返回false
-
在配置类设置当前访问地址有哪些权限
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 自定义登录页面 .loginPage("/login.html") //登录页面设置 .loginProcessingUrl("/user/login") // 登录访问路径 .defaultSuccessUrl("/login/index").permitAll() // 登录成功之后跳转路径 .and().authorizeRequests() .antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证 .antMatchers("/login/index").hasAuthority("admin")// 设置admin权限 ----------- .anyRequest().authenticated() .and().csrf().disable();// 关闭csrf防护 }
-
在userDetailService,返回user对象的权限
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper<com.zhj.springsecuritydemo.entity.User> wrapper = new QueryWrapper<>(); wrapper.eq("name",username); com.zhj.springsecuritydemo.entity.User user = userMapper.selectOne(wrapper); if(user == null){ throw new UsernameNotFoundException("用户名不存在"); } return new User(user.getName() ,new BCryptPasswordEncoder().encode(user.getPwd()) ,AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); }
hasAnyAuthority :多个权限
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() // 自定义登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/login/index").permitAll() // 登录成功之后跳转路径
.and().authorizeRequests()
.antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
//.antMatchers("/login/index").hasAuthority("admin")// 只有一个主体admin权限 -----------
.antMatchers("/login/index").hasAnyAuthority("admin,manage")// 多个权限 -----------
.anyRequest().authenticated()
.and().csrf().disable();// 关闭csrf防护
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<com.zhj.springsecuritydemo.entity.User> wrapper = new QueryWrapper<>();
wrapper.eq("name",username);
com.zhj.springsecuritydemo.entity.User user = userMapper.selectOne(wrapper);
if(user == null){
throw new UsernameNotFoundException("用户名不存在");
}
return new User(user.getName()
,new BCryptPasswordEncoder().encode(user.getPwd())
,AuthorityUtils.commaSeparatedStringToAuthorityList("manage"));
}
hasRole : 针对某一个角色
.antMatchers("/login/index").hasRole("sale") // 只有一个角色
return new User(user.getName()
,new BCryptPasswordEncoder().encode(user.getPwd())
,AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale")); // 注意前缀
hasAnyRole : 针对多个角色,多个角色满足其中一个角色即可
类似上面
自定义无权限访问页面
configure(HttpSecurity http)方法中添加:
// 自定以没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1> 哈哈 您没有权限</h1>
</body>
</html>
注解的使用
@Secured
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀 “_ROLE”;
使用注解先要开启注解功能 启动类配置:
@EnableGlobalMethodSecurity(securedEnabled=true)
然后对应controller方法 使用注解 例如:
@Secured("ROLE_sale") @GetMapping("update") public String update(){ return "hello update"; }
@PreAuthorize
注解适合进入方法的权限验证, @Preauthorize可以将登录用户的role/permission参数传到方法中
进入方法之前做权限验证
使用注解先要开启注解功能 启动类配置:
@EnableGlobalMethodSecurity(prePostEnabled = true)
然后对应controller方法 使用注解 例如:
@PreAuthorize("hasAuthority('admin')") @GetMapping("insert") public String insert(){ return "hello insert"; }
@PostAuthorize
注解使用不多,在方法执行后进行权限验证,适合验证带有返回值的权限
首先开启注解
然后对应controller方法 使用注解 如:
@PostAuthorize("hasAuthority('admin')") @GetMapping("delete") public String delete(){ System.out.println("hello world"); return "hello delete"; }
用户点击退出操作
在配置类中配置:
http.logout().logoutUrl("/logout").logoutSuccessUrl("/login/hello").permitAll();
测试:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout().logoutUrl("/logout").logoutSuccessUrl("/login/hello").permitAll();
// 自定以没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin() // 自定义登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/success.html").permitAll() // 登录成功之后跳转路径
.and().authorizeRequests()
.antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
.antMatchers("/login/index").hasAuthority("admin")// 只有一个主体admin权限 -----------
//.antMatchers("/login/index").hasAnyAuthority("admin,manage")// 多个主体权限 -----------
.antMatchers("/login/index").hasRole("sale") // 只有一个角色
.anyRequest().authenticated()
.and().csrf().disable();// 关闭csrf防护
}
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
login Success
<br>
<a href="/logout">退出</a>
</body>
</html>
自动登录
- cookie
- security框架提供的技术
security实现
-
配置类(securityConfig), 注入数据源 配置数据库连接对象
@Autowired private DataSource datasource; // 配置数据库Bean对象 public PersistentTokenRepository persistentTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(datasource); jdbcTokenRepository.setCreateTableOnStartup(true); // 自动建表 return jdbcTokenRepository; }
-
configure(HttpSecurity http) 配置记住我 ,有效时长 ,userDetailService
@Override protected void configure(HttpSecurity http) throws Exception { http.logout().logoutUrl("/logout").logoutSuccessUrl("/login/hello").permitAll(); // 自定以没有权限访问的页面 http.exceptionHandling().accessDeniedPage("/unauth.html"); http.formLogin() // 自定义登录页面 .loginPage("/login.html") //登录页面设置 .loginProcessingUrl("/user/login") // 登录访问路径 .defaultSuccessUrl("/success.html").permitAll() // 登录成功之后跳转路径 .and().authorizeRequests() .antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证 .antMatchers("/login/index").hasAuthority("admin")// 只有一个主体admin权限 ----------- //.antMatchers("/login/index").hasAnyAuthority("admin,manage")// 多个主体权限 ----------- .antMatchers("/login/index").hasRole("sale") // 只有一个角色 .anyRequest().authenticated() .and().rememberMe().tokenRepository(persistentTokenRepository()) //记住我 .tokenValiditySeconds(60) // 设置时长 .userDetailsService(userDetailsService) // 操作数据库 .and().csrf().disable();// 关闭csrf防护 }
-
html
<pre> <form action="/user/login" method="post"> 用户名: <input type="text" name="username"> <!-- name="username" 不允许变--> 密码: <input type="password" name="password"> <!-- name="password" 不允许变--> <input type="checkbox" name="remember-me"> 记住我 <!-- name="remember-me" 不允许变--> <input type="submit" value="登录" > </form> </pre>
-
愉快的测试吧
CSRF
跨站请求伪造(英文:Cross-site request forgery),也被称为one-click attack 或者 session riding,通常缩写为CSRF或者XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法,跟跨网站脚本(XSS相比),XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。
网站请求攻击,简单的说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如 发邮件,发消息,甚至转账或购买行为),由于浏览器曾经认证过,所以被访问的网站会认为是正真的用户操作而去运行,这里用了web中用户身份验证的一个漏洞: 简单的身份验证只能保证请求发自某个用户的浏览器,去不能保证请求本身是用户自哪发的。
从Spring 4.0 开始,默认情况下会启动CSRF保护,以防止攻击应用程序,Spring Security CSRF会针对PATCH、POST、PUT和DELETE方法进行保护
如何配置?
添加依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
默认是开启的 注释disable
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout().logoutUrl("/logout").logoutSuccessUrl("/login/hello").permitAll();
// 自定以没有权限访问的页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin() // 自定义登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/success.html").permitAll() // 登录成功之后跳转路径
.and().authorizeRequests()
.antMatchers("/", "/login/hello").permitAll()// 设置路径可以直接访问, 不需要认证
.antMatchers("/login/index").hasAuthority("admin")// 只有一个主体admin权限 -----------
//.antMatchers("/login/index").hasAnyAuthority("admin,manage")// 多个主体权限 -----------
.antMatchers("/login/index").hasRole("sale") // 只有一个角色
.anyRequest().authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository()) //记住我
.tokenValiditySeconds(60) // 设置时长
.userDetailsService(userDetailsService) // 操作数据库
//.and().csrf().disable();
}
表单中配置
<form action="/user/login" method="post"> <!--this-->
<!--加上隐藏项-->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
用户名: <input type="text" name="username"> <!-- name="username" 不允许变-->
密码: <input type="password" name="password"> <!-- name="password" 不允许变-->
<input type="checkbox" name="remember-me"> 记住我 <!-- name="remember-me" 不允许变-->
<input type="submit" value="登录" >
</form>