SpringSecurity模板/快速开始(超详细)基于SpringBoot
文章介绍
本文只有SpringSecurity的解释,其他内容请自行配置。快速使用请直接阅读前三个代码块,前三个代码块包含后面的内容介绍。
一、依赖
<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>
<!--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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
二、在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_admin"); // 为用户授权 ROLE_xxx 为用户授予xxx角色
//for(Role role:roleList){ 如果数据库中有角色表
// SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_"+role.getName());
// auths.add(simpleGrantedAuthority);
//}
//for(Menu menu:menuList){ 如果数据库存中有用户对应权限
// SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(menu.getPermission());
// auths.add(simpleGrantedAuthority);
//}
return new User(users.getUsername(),
new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}
}
三、在config目录下创建
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 注入 PasswordEncoder 类到 spring 容器中
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PersistentTokenRepository tokenRepository;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 退出
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();
// 配置认证
http.formLogin()
.loginPage("/index") // 配置哪个 url 为登录页面
.loginProcessingUrl("/login") // 设置哪个是登录的 url。
.successForwardUrl("/success") // 登录成功之后跳转到哪个 url
.failureForwardUrl("/fail")// 登录失败之后跳转到哪个 url
.usernameParameter("xxxname") // 设置表单提交的用户名和密码
.passwordParameter("xxxpwd");
http.authorizeRequests()
.antMatchers("/layui/**","/index").permitAll() //表示配置无需验证的请求路径 // 指定 URL 无需保护。
.anyRequest().authenticated() // 其他请求//需要认证
//.antMatchers("/findAll").hasAuthority("admin") //当前登录用户,只有具有admin权限才可以访问这个路径(需要为用户设置权限)
//.antMatchers("/find").hasAnyAuthority("role,admin") //当前登录用户,具有其中一项权限即可
.antMatchers("/findAll").hasRole("admin") // 需要用户具有admin角色
.antMatchers("/find").hasAnyRole("admin,role"); // 需要用户具有其中至少一种角色
// 开启记住我功能
http.rememberMe()
.tokenRepository(tokenRepository)
.tokenValiditySeconds(60) // 设置有效时长,单位是以秒为单位
.userDetailsService(userDetailsService);
// 关闭 csrf
http.csrf().disable();
}
}
@Configuration
public class BrowserSecurityConfig {
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new
JdbcTokenRepositoryImpl();
// 赋值数据源
jdbcTokenRepository.setDataSource(dataSource);
// 自动创建表,第一次执行会创建,以后要执行就要删除掉!
jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
四、注解使用
1.@Secured
使用注解先要开启注解功能!
@EnableGlobalMethodSecurity(securedEnabled=true)
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
例如:在控制器方法上添加注解
// 测试注解:
@RequestMapping("testSecured")
@ResponseBody
@Secured({
"ROLE_normal","ROLE_admin"})
public String helloUser() {
return "hello,user";
}
2.@PreAuthorize
先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用
户的 roles/permissions 参数传到方法中。
例如:
@RequestMapping("/preAuthorize")
@ResponseBody
//@PreAuthorize("hasRole('ROLE_管理员')")
@PreAuthorize("hasAnyAuthority('admin')")
public String preAuthorize(){
System.out.println("preAuthorize");
return "preAuthorize";
}
3.@PostAuthorize
先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.
例如:
@RequestMapping("/testPostAuthorize")
@ResponseBody
@PostAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
System.out.println("test--PostAuthorize");
return "PostAuthorize";
}
4.@PostFilter
@PostFilter :权限验证之后对数据进行过滤 留下用户名是 admin1 的数据
表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素
例如:
@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;
}
5.@PreFilter
@PreFilter: 进入控制器之前对数据进行过滤
例如:
@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;
}
五、基于数据库的记住我
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.编写配置类
在config中创建配置类
@Configuration
public class BrowserSecurityConfig {
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new
JdbcTokenRepositoryImpl();
// 赋值数据源
jdbcTokenRepository.setDataSource(dataSource);
// 自动创建表,第一次执行会创建,以后要执行就要删除掉!
jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
3.修改安全配置类
在上述 public class SecurityConfig extends WebSecurityConfigurerAdapter类中
@Autowired
private UsersServiceImpl usersService;
@Autowired
private PersistentTokenRepository tokenRepository;
// 开启记住我功能
http.rememberMe()
.tokenRepository(tokenRepository)
.userDetailsService(usersService)
.tokenValiditySeconds(60); // 设置有效时长,单位是以秒为单位
4.页面添加记住我复选框
记住我:<input type="checkbox"name="remember-me"title=“记住密码”/>
此处:name 属性值必须位 remember-me.不能改为其他值
CSRF
1.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 方法进行防护。
2.使用
在登录页面添加一个隐藏域:
<input type="hidden"th:if="${_csrf}!=null"th:value="${_csrf.token}"name="_csrf"/>
在SecurityConfig中关闭安全配置的类中的 csrf
// http.csrf().disable();