# spring_security_demo
**Repository Path**: johndriver/spring_security_demo
## Basic Information
- **Project Name**: spring_security_demo
- **Description**: Spring Security 的范例
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-06-02
- **Last Updated**: 2021-06-03
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# SpringSecurity
## 基本使用
* 1.maven引入
```xml
org.springframework.boot
spring-boot-starter-security
```
* 2.访问
```text
/login ->springSecurity内置登录页面(用户名:user,密码:控制台打印出来的随机密码)
```
## 自定义页面(登录、登录失败、登录成功页面)
```text
/login -> .loginPage("/login.html"):找自定义的login.html页面 -> .antMatchers("/login.html").permitAll():开放/login.html访问
-> .loginProcessingUrl("/login"):指定/login的post请求(login.html的form里边的action值)会走UserDetailsService的登录验证逻辑
-> .successForwardUrl("/toMain") 登录成功跳转方法,通过controller的post方法
-> .failureForwardUrl("/toError") 登录失败跳转方法,通过controller的post方法 ->.antMatchers("/login.html").permitAll() 放性自定义错误页面
```
* 1.页面 `login.html`
`username` `password` 固定写法,不可修改(后面有可修改犯法)
```xml
```
* 2.配置 `SecurityConfig`
```java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//表单登录配置
http.formLogin()
//当发现/login时认为是登录,必须与表单提交的地址一样,就可以去执行UserDetailsServiceImpl
.loginProcessingUrl("/login")
//自定义登录页面
.loginPage("/login.html")
;
//认证授权
http.authorizeRequests()
//login.html error.html不需要认证
.antMatchers("/login.html").permitAll()
//其他任何请求都需要认证,必须登录后才可以访问
.anyRequest().authenticated()
;
//关闭csrf防护
http.csrf().disable();
}
}
```
* 3.自定义登录逻辑
* 3.1 自定义密码加密
`SecurityConfig` 中添加
```text
// 密码加密器
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
```
加密器的方法
```text
passwordEncoder.encode(原始密码):加密原始密码
passwordEncoder.matches(原始密码,加密后的密码):验证原始密码和加密密码是否匹配
passwordEncoder.upgradeEncoding():如果解析的密码能够再次进行解析且达到更 安全的结果则返回 true,否则返回 false。默认返回 false。
```
* 3.2 自定义 `UserDetailsService`
```java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("UserDetailsServiceImpl.loadUserByUsername");
//1.查数据库,判断用户名是否存在
//"admin":模拟的数据库查出来的密码
if(!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在");
}
//2.模拟数据库查询的密码(注册时加密过)解析,或者直接放入构造方法
String password = passwordEncoder.encode("123");
//3.组合返回User对象
//"admin,normal,ROLE_abc":模拟数据库查询到的权限
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));
}
}
```
* 4.登录成功跳转
* 4.1 `successForwardUrl`
SecurityConfig
```text
.successForwardUrl("/toMain")
```
controller
```text
@PostMapping("/toMain")
public String toMain(){
System.out.println("执行登录方法");
return "redirect:main.html";
}
```
main.html
* 4.2 自定义登录成功处理器 `AuthenticationSuccessHandler`
MyAuthenticationSuccessHandler
```java
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//可以取得用户信息,用于页面
User user = (User) authentication.getPrincipal();
System.out.println(user.getUsername());
System.out.println(user.getPassword());
System.out.println(user.getAuthorities());
response.sendRedirect(url);
}
}
```
SecurityConfig
```text
.successHandler(new MyAuthenticationSuccessHandler("main.html"))
```
* 4.登录失败跳转
* 4.1 failureForwardUrl
SecurityConfig
```text
.failureForwardUrl("/toError")
.antMatchers("/login.html").permitAll()
```
controller
```text
@PostMapping("/toError")
public String toError(){
System.out.println("执行登录方法");
return "redirect:error.html";
}
```
error.html
* 4.2 自定义登录失败处理器 `AuthenticationFailureHandler`
MyAuthenticationFailureHandler
```text
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private final String url;
public MyAuthenticationFailureHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//可以获取异常信息
e.getMessage();
response.sendRedirect(url);
}
}
```
SecurityConfig
```text
.failureHandler(new MyAuthenticationFailureHandler("/error.html"))
```
## 授权认证配置
```java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//...
//认证授权
http.authorizeRequests()
//login.html error.html不需要认证
.antMatchers("/error.html").permitAll()
.antMatchers("/login.html").permitAll()
//放行静态资源
.antMatchers("/js/**","/css/**","/images/**").permitAll()
.antMatchers("/**/*.jpg").permitAll()
.antMatchers(HttpMethod.GET,"/**/*.jpg").permitAll()
//正则方式
.regexMatchers(".+[.]jpg").permitAll()
//mvcMatchers
//.mvcMatchers("/demo").servletPath("/xxx").permitAll()
.antMatchers("/main1.html").hasAuthority("admin")
//角色ROLE_rolename在这里去掉ROLE_
.antMatchers("/main1.html").hasRole("rolename")
//任何请求
.anyRequest()
//都需要认证,必须登录后才可以访问
.authenticated()
;
//关闭csrf防护
http.csrf().disable();
}
}
```
assess
```text
效果一样
.antMatchers("/error.html").permitAll()
.antMatchers("/error.html").access("permitAll")
```
自定义权限认证
```text
接口
public interface MyService {
boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
实现
@Service
public class MyServiceImpl implements MyService {
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object obj = authentication.getPrincipal();
if(obj instanceof UserDetails){
UserDetails details = (UserDetails) obj;
Collection extends GrantedAuthority> authorities = details.getAuthorities();
return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
}
return false;
}
}
SecurityConfig配置
.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)")
```
权限
```text
permitAll:允许所有人访问
denyAll:任何人都不允许
anonymous:可以匿名访问(与permitAll不同点:这个会到拦截链中)
authenticated:需要认证
fullyauthenticated:(勾选记住我不行,需要一步步登录)需要完全认证
rememgetMe:勾选记住我
```
角色权限判断
```text
//权限
hasAuthority(String auth)
hasAnyAuthority(String... auth)
//角色
hasRole(String role)
hasAnyRole(String... role)
//ip
hasIpAddress()
```
## 基于注解的访问控制
### @Secured
```text
APP
@EnableGlobalMethodSecurity(securedEnabled = true)
方法或类
@Secured("ROLE_abc")
@PostMapping("/toMain")
public String toMain(){
System.out.println("执行登录方法");
return "redirect:main.html";
}
```
### @PreAuthorize/@PostAtrhrize
PreAuthorize:执行方法前验证权限
```text
app:
@EnableGlobalMethodSecurity(prePostEnabled = true)
方法或类
@PreAuthorize("hasRole('abc')")//@PreAuthorize("hasRole('ROLE_abc')")也允许
@PostMapping("/toMain")
public String toMain(){
System.out.println("执行登录方法");
return "redirect:main.html";
}
```
@PostAtrhrize执行方法后验证权限
## 自定义权限受限页面(403页面)
* 自定义 `AccessDeniedHandler`
```java
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
//设置响应状态码
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setHeader("Content-Type","application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("{\"status\":\"error\",\"msg\":\"权限不足\"}");
writer.flush();
writer.close();
}
}
```
配置SecurityConfig
```text
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
//异常处理
http.exceptionHandling()
//访问权限304
.accessDeniedHandler(myAccessDeniedHandler);
```
## RememberMe功能实现
* 1.pom依赖
```text
mysql
mysql-connector-java
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.3
```
* 2.配置
建立数据库:security
```text
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url= jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Shanghai
spring.datasource.username= root
spring.datasource.password= root
```
* 3.remenber-me
```xml
```
4. 配置
```text
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private DataSource dataSource;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
@Bean
PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//自动建表,第二次启动要注释掉
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.rememberMe()
//过期时间(默认两周)
.tokenValiditySeconds(60)
//.rememberMeParameter() //表单参数 默认remember-me
//DetailsService登录逻辑
.userDetailsService(userDetailsService)
//存储token的Repository
.tokenRepository(persistentTokenRepository)
;
}
```
## thymeleaf(前后端不分离项目)
```text
org.thymeleaf.extras
thymeleaf-extras-springsecurity5
org.springframework.boot
spring-boot-starter-thymeleaf
```
页面获取信息
```html
Title
账号 123
账号123
凭证123
权限123
客户端地址123
sessionId123
```
权限判断
```html
```
## csrf跨站请求访问
spring security默认开启csrf防护:要求访问带上参数:_csrf=token(服务器产生)
```html
```
```text
@RequestMapping("/showLogin")
public String showLogin(){
return "login";
}
```
```text
.antMatchers("/showLogin").permitAll()
```
```text
/showLogin ->开放/showLogin ->crolller->login.html获得服务端的生成的_csrf(${_csrf.token})
->请求时带上ame="_csrf" th:value="${_csrf.token}"->符合csrf防护
```