1. 概念入门
概念: spring-security是spring用于提供声明式安全访问控制解决方案的安全框架:
- 认证:核心是比对用户的账号密码身份等信息,而验证指的是某些规则的匹配。
- 授权:为用户赋权或赋角色。
流程: 创建springboot-jar项目 springboot2-security:
- 配置项目依赖:idea直接选择
security/spring security依赖:spring-boot-starter-security:security核心包。spring-boot-starter-web:spring-web核心包。spring-boot-starter-thymeleaf:thymeleaf核心包,选用。thymeleaf-extras-springsecurity5:thymeleaf和security整合包,选用。spring-security-test:security测试包。
- 主配添加thymeleaf相关配置:关闭缓存,前后缀,编码,静态资源路径等。
- 开发测试页面
templates/start.html。 - cli:
start.html:发现整个项目资源都被security保护起来:- 所有请求重定向到security默认的登录路由
/login并展示security内置的登录页面。 - 输入账号
user和在日志中生成的随机密码,点击登录。 - 登陆成功后,security默认访问登陆前一次的URL,404时默认寻找
error.html。 - 测试时注意浏览器缓存问题,浏览器完全关闭时视为注销登陆。
- 所有请求重定向到security默认的登录路由
- 自定义账密:在主配中设置自定义账密后,日志将不再生成随机登录密码:
spring.security.user.name=yap:自定义security账号。spring.security.user.password=123:自定义security密码。spring.security.user.roles=TEST:自定义账号角色,逗号分隔,不要使用ROLE_前缀。
- cli:
start.html:输入自定义账号密码。
源码: /springboot/
- res:
pom.xml
<!--spring-boot-starter-security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--spring-boot-starter-thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--thymeleaf-extras-springsecurity5-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--spring-boot-devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--spring-security-test-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
- web:
start.html
<h1>start.html</h1>
- web:
error.html
<h1>404 or auth-fail...</h1>
2. 资源放行
概念: 对于首页,广告页等请求不需要security保护,需要放行:
- 开发配置类
c.y.s.config.SecurityConfig:- 标记
@Configuration声明为配置类。 - 标记
@EnableWebSecurity以启用security功能。
- 标记
- 配置类重写
o.s.s.c.a.w.c.WebSecurityConfigurerAdapter.configure():http.authorizeRequests():权限请求设置。antMatchers(URL).permitAll():表示放行某些指定规则的请求URL。anyRequest().authenticated():表示其余的任何请求都需要认证。and():用于连接并列关系的配置。formLogin():用于恢复security表单登陆功能。
- cli:
/index.html,/和/ad.html直接放行,而其他URL仍被security保护。 源码: /springboot/ - src:
c.y.s.config.SecurityConfig
package com.yap.springboot2security.config;
import com.yap.springboot2security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @Author Yap
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/index.html", "/aaa.html").permitAll().anyRequest().authenticated().and().formLogin();
}
}
- web:
index.html
<h1>index.html</h1>
- web:
ad.html
<h1>ad.html: advertising!</h1>
3. 自定义登陆页面
流程:
- 开发动作类
c.y.s.controller.UserController:loginRouting():用于提供登录页面/templates/login.html的路由。mainSuccess():用于提供登录成功页面/templates/main.html的路由。SecurityContextHolder.getContext().getAuthentication():登陆后可随时获取security认证信息。
- 在
configure()中的formLogin()后额外配置,所有URL都建议使用动作方法的路由:loginPage(URL):设置自定义登录页面路由。loginProcessingUrl(URL):设置登录处理路径,命名随意。successForwardUrl(URL):设置登录成功页面的路由,缺省跳转至上次的请求路径。failureForwardUrl(URL):设置登录失败页面的路由,缺省跳转至loginPage()指定的路由。permitAll():表示放行以上全部路径或路由。csrf().disable():禁止跨域防护,即允许跨域请求,缺省时自定义表单登录功能失效。
- 开发登录页面
templates\login.html:- 引入thymeleaf标签:
xmlns:th="https://www.thymeleaf.org"。 - 设计表单,
action的值必须和loginProcessingUrl()配置的路径一致。 - 账号和密码控件建议命名为
username和password。 ${param.error}:获取账密错误时,security跳转至loginPage()指定的路由时附加的?error。${param.logout}:获取注销登录时,security跳转至loginPage()指定的路由时附加的?logout。
- 引入thymeleaf标签:
- 开发登录成功页面
templates\main.html:- 引入security标签:
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"。 sec:authorize="isAuthenticated()":仅在认证成功时才会展示标签及标签中的内容。sec:authentication="name":获取认证信息中的账号信息,其余同理。sec:authentication="principal":获取认证信息中的主要信息,如密码(受保护),过期时间等。 源码: /springboot/
- 引入security标签:
- res:
c.y.s.controller.UserController
package com.yap.springboot2security.controller;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author yap
*/
@Controller
@RequestMapping("/api/user")
public class UserController {
@RequestMapping("login-routing")
public String loginRouting() {
System.out.println("loginRouting()...");
return "login";
}
@RequestMapping("main-routing")
public String mainRouting() {
System.out.println("mainRouting()...");
System.out.println(SecurityContextHolder.getContext().getAuthentication());
return "main";
}
}
- src:
SecurityConfig修改
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/index.html", "/ad.html").permitAll()
.anyRequest().authenticated();
http.csrf().disable();
http.formLogin()
.loginPage("/api/user/login-routing")
.loginProcessingUrl("/login")
.successForwardUrl("/api/user/main-routing")
.permitAll();
- web:
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>custom login page</title>
</head>
<body>
<form action="/login" method="post">
<label><input type="text" name="username"/></label>
<label><input type="password" name="password"/></label>
<button type="submit">登录</button>
</form>
<h4 th:if="${param.error}">账号或密码错误...</h4>
<h4 th:if="${param.logout}">您已注销...</h4>
</body>
</html>
- web:
main.html
<!DOCTYPE html>
<html lang="en" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>custom main page</title>
</head>
<body>
<section sec:authorize="isAuthenticated()">
<h1>main.html</h1>
<ul>
<li>username: <strong sec:authentication="name"></strong></li>
<li>roles: <strong sec:authentication="principal.authorities"></strong></li>
<li>principal: <strong sec:authentication="principal"></strong></li>
</ul>
<!-- <ul>
<li>登录账号为: <strong sec:authentication="name"></strong></li>
<li>principal: <strong sec:authentication="principal"></strong></li>
<li>登录密码为: <strong sec:authentication="principal.password"></strong></li>
<li>登录角色为: <strong sec:authentication="principal.authorities"></strong></li>
<li>账号仍可用: <strong sec:authentication="principal.enabled"></strong></li>
<li>凭证未过期: <strong sec:authentication="principal.credentialsNonExpired"></strong></li>
<li>账号未过期: <strong sec:authentication="principal.accountNonExpired"></strong></li>
<li>账号未锁定: <strong sec:authentication="principal.accountNonLocked"></strong></li>
</ul>-->
</section>
</body>
</html>
4. 配置用户数据
流程: 自定义用户数据,如账密,角色和权限等,此时主配中配置的账密即使保留也会失效:
- 开发实体类
c.y.s.pojo.Permission/Role/User:集合属性需要给初始值,否则空指针。 - 开发工具类
c.y.s.util.DataUtil模拟数据库数据:admin/123为ROLE_ADMIN角色,拥有insert/select/update/delete权限。zhaosi/123为ROLE_COMM角色,拥有select权限。
- 开发配置类
c.y.s.c.PasswordEncoderConfig:security规定密码必须进行加密才可使用:- security默认的
{noop}前缀加密方式不安全,建议更换为BCRYPT加密方式:z-res/常用加密算法.md
- IOC
o.s.s.c.p.PasswordEncoder:对其实现类o.s.s.c.b.BCryptPasswordEncoder进行实例化。
- security默认的
- 开发业务类
c.y.s.s.UserDetailsServiceImpl:通过构造注入密码加密类。 - 重写业务方法
o.s.s.c.u.UserDetailsService.loadUserByUsername():- 根据前端传递过来的账号,从数据库中获取对应的
User实体。 passwordEncoder.encode(password):获取密码并对其进行BCRYPT加密。- 构建
UserDetails接口子类o.s.s.c.u.User对象并返回,构造需要账密和角色/权限集合。
- 根据前端传递过来的账号,从数据库中获取对应的
- 开发配置类
c.y.s.c.SecurityConfig:用自己的业务类覆盖security默认的业务类:- 通过属性注入业务类,构造器注入会产生A构造注入B,B构造注入A的环形注入问题,导致服务器启动崩溃。
- 通过属性或构造注入密码加密类。
- 使用
configureGlobal()注入AuthenticationManagerBuilder对象。 builder.userDetailsService():使用自定义业务类以覆盖security默认使用的业务类。service.passwordEncoder(passwordEncoder):设置密码加密方式。
- 分别使用
admin/123和zhaosi/123登陆并查看角色/权限信息,以及观察两个密码123加密后的值是否一样。
源码: /springboot/
- res:
c.y.s.pojo.Permission
package com.yap.springboot2security.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author yap
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Permission implements Serializable {
private String keyword;
}
- res:
c.y.s.pojo.Role
package com.yap.springboot2security.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
* @author yap
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Role implements Serializable {
private String keyword;
private Set<Permission> permissions = new HashSet<>();
}
- res:
c.y.s.pojo.User
package com.yap.springboot2security.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
* @author yap
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable {
private String username;
private String password;
private Set<Role> roles = new HashSet<>();
}
- res:
c.y.s.util.DataUtil
package com.yap.springboot2security.util;
import com.yap.springboot2security.pojo.User;
import com.yap.springboot2security.pojo.Role;
import com.yap.springboot2security.pojo.Permission;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author yap
*/
public class DataUtil {
public static Map<String, User> getData() {
Map<String, User> usersFromDb = new ConcurrentHashMap<>();
// permission list
Permission insertPermission = new Permission("insert");
Permission selectPermission = new Permission("select");
Permission updatePermission = new Permission("update");
Permission deletePermission = new Permission("delete");
// role list
Role adminRole = new Role();
adminRole.setKeyword("ROLE_ADMIN");
adminRole.getPermissions().add(insertPermission);
adminRole.getPermissions().add(selectPermission);
adminRole.getPermissions().add(updatePermission);
adminRole.getPermissions().add(deletePermission);
Role commRole = new Role();
commRole.setKeyword("ROLE_COMM");
commRole.getPermissions().add(selectPermission);
// user list
User admin = new User();
admin.setUsername("admin");
admin.setPassword("123");
admin.getRoles().add(adminRole);
usersFromDb.put(admin.getUsername(), admin);
User zhaosi = new User();
zhaosi.setUsername("zhaosi");
zhaosi.setPassword("123");
zhaosi.getRoles().add(commRole);
usersFromDb.put(zhaosi.getUsername(), zhaosi);
return usersFromDb;
}
}
- res:
c.y.s.c.PasswordEncoderConfig
package com.yap.springboot2security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author yap
*/
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- res:
c.y.s.s.UserDetailsServiceImpl
package com.yap.springboot2security.service;
import com.yap.springboot2security.pojo.Permission;
import com.yap.springboot2security.pojo.Role;
import com.yap.springboot2security.pojo.User;
import com.yap.springboot2security.util.DataUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author yap
*/
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
private PasswordEncoder passwordEncoder;
@Autowired
public UserDetailsServiceImpl(PasswordEncoder passwordEncoder){
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDetails loadUserByUsername(String username) {
User user = DataUtil.getData().get(username);
if (user == null) {
throw new RuntimeException("username not exist...");
}
String password = passwordEncoder.encode(user.getPassword());
System.out.println("password: " + password);
return new org.springframework.security.core.userdetails.User(username, password, getAuthoritiesByUser(user));
}
private List<GrantedAuthority> getAuthoritiesByUser(User user) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : user.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role.getKeyword()));
for (Permission permission : role.getPermissions()) {
authorities.add(new SimpleGrantedAuthority(permission.getKeyword()));
}
}
return authorities;
}
}
- res:
c.y.s.c.SecurityConfig
package com.yap.springboot2security.config;
import com.yap.springboot2security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author yap
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsServiceImpl).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/index.html", "/ad.html").permitAll()
.anyRequest().authenticated();
http.csrf().disable();
http.formLogin()
.loginPage("/api/user/login-routing")
.loginProcessingUrl("/login")
.successForwardUrl("/api/user/main-routing")
.permitAll();
}
}
常用加密算法
1.对称加密算法
- 代表:3DES / AES / DES等
- 举例:将
a变成4,将b变成5,于是密码ab被加密成了45。 - 总结:加密规则规律固定,了解规律即可破解,安全性极低。
2.单向HASH加密算法
- 代表:MD5 / SHA1等
- 举例:将
a变为#,将b变成1,于是密码ab被加密成了#1。 - 总结:HASH算法无规律,无法直接反向破解,但可通过建立彩虹表进行查表破解,如:
- 我的密码经过MD5加密后变成
Q!#FV!#0G! - 你的密码经过MD5加密后也是
Q!#FV!#0G! - 于是你自然知道我的密码是什么了,虽然破解费点时间,但也不是绝对的安全。
- 我的密码经过MD5加密后变成
3.浮动HASH加密算法
-
代表:BCRYPT:
-
举例:将
a变为k,将b变成6,于是密码ab被加密成了k6:- 然后再加上一些随机salt(盐份),变成
k6#$ - 由于每次添加的slat都是随机的,所以彩虹表暴力破解也无能为力。
- 然后再加上一些随机salt(盐份),变成
-
总结:BCRYPT是HASH算法的升级版,将salt随机混入加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题,是目前最安全的算法。
5. 路由访问权限
概念: 动作方法可以针对不同的角色/权限进行选择性开放:
- 开发测试页面
templates/auth.html:添加一些测试超链接。 - 开发动作类
c.y.s.controller.AuthController:动作方法标记@PreAuthorize以设置访问规则:@PreAuthorize("hasRole(A)"):仅A角色可以访问我。@PreAuthorize("hasAnyRole(A, B)"):仅A或B角色可以访问我。@PreAuthorize("hasAuthority(A)"):仅A权限可以访问我。@PreAuthorize("hasAnyAuthority(A, B)"):仅A或B权限可以访问我。
- 配置类中开启
@PreAuthorize注解支持:@EnableGlobalMethodSecurity(prePostEnabled=true)。 - 开发页面
auth-success.html,测试成功时跳转,失败时默认自动寻找error.html,缺省报错。 - cli:
auth.html测试不同用户的动作方法访问权限。
源码: /springboot/
- src:
c.y.s.controller.AuthController
package com.yap.springboot2security.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author yap
*/
@Controller
@RequestMapping("/api/auth")
public class AuthController {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping("admin")
public String admin() {
System.out.println("admin()...");
return "auth-success";
}
@PreAuthorize("hasRole('ROLE_COMM')")
@RequestMapping("comm")
public String comm() {
System.out.println("comm()...");
return "auth-success";
}
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_COMM')")
@RequestMapping("all")
public String all() {
System.out.println("all()...");
return "auth-success";
}
@PreAuthorize("hasAuthority('select')")
@RequestMapping("select")
public String select() {
System.out.println("select()...");
return "auth-success";
}
@PreAuthorize("hasAnyAuthority('delete','update','insert')")
@RequestMapping("dml")
public String dml() {
System.out.println("dml()...");
return "auth-success";
}
}
- web:
auth.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>auth.html</h1>
<ul>
<li><a href="/api/auth/admin">has role: [ROLE_ADMIN]</a></li>
<li><a href="/api/auth/comm">has role: [ROLE_COMM]</a></li>
<li><a href="/api/auth/all">has role: [ROLE_ADMIN/ROLE_COMM]</a></li>
<li><a href="/api/auth/select">has permission: [select]</a></li>
<li><a href="/api/auth/dml">has permission: [insert/update/delete]</a></li>
</ul>
</body>
</html>
6. 友好注销用户
流程: 浏览器完全退出时表示注销当前登录的用户,但不够友好:
- 开发页面
logout.html页面:布局注销按钮。 - 在动作类
c.y.s.controller.UserController中添加一个对应注销请求的路由方法,空方法并返回void即可。 - 在配置类
.formLogin()后额外配置:.and().logout():定义注销的相关配置。.logoutUrl():必须一致和注销请求URL,以及动作方法路由保持一致。.logoutSuccessUrl():注销成功后的跳转页面,建议使用动作方法路由,缺省调用loginPage()指定的页面。.deleteCookies("JSESSIONID"):删除掉cookie中的JSESSIONID,否则连接一直维持,不算注销。.invalidateHttpSession(true):使session失效。
- 登录再注销,跟踪
F12/Application/Cookie/JSESSIONID值并测试注销后的账户是否仍可访问项目资源。 源码: /springboot/ - web:
logout.html
<h1>
<a href="/api/user/logout">注销</a>
</h1>
- src:
c.y.s.controller.UserController
@RequestMapping("logout")
public void logout() {}
- src:
c.y.s.config.SecurityConfig
.and().logout()
.logoutUrl("/api/user/logout")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.permitAll();
7. 重写登录业务
流程: 若希望登陆成功或失败时返回JSON数据而不跳页,则需重写登录成功或失败的处理器类:
- 开发登陆成功处理器
c.y.s.handler.CustomLoginSuccessHandler:- 重写
o.s.s.w.a.SavedRequestAwareAuthenticationSuccessHandler.onAuthenticationSuccess()。 new DefaultRedirectStrategy().sendRedirect(req, resp, URL):可使用重定向跳页。resp.getWriter().write():可使用repsonse回写数据。
- 重写
- 开发登陆失败处理器
c.y.s.handler.CustomLoginFailureHandler:- 重写
o.s.s.w.a.SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure()。 new DefaultRedirectStrategy().sendRedirect(req, resp, URL):可使用重定向跳页。resp.getWriter().write():可使用repsonse回写数据。
- 重写
- 在配置类中注入这两个处理器类。
- 将配置类
configure()中的successForwardUrl()替换为:.successHandler(customLoginSuccessHandler):设置自定义登录成功处理器。.failureHandler(customLoginFailureHandler):设置自定义登录失败处理器。 源码: /springboot/
- src:
c.y.s.handler.CustomLoginSuccessHandler
package com.yap.springboot2security.handler;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author yap
*/
@Component
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException {
System.out.println("CustomLoginSuccessHandler...");
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().write("login success!");
}
}
- src:
c.y.s.handler.CustomLoginFailureHandler
package com.yap.springboot2security.handler;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author yap
*/
@Component
public class CustomLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException exception) throws IOException {
System.out.println("CustomLoginFailureHandler...");
resp.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().write("login fail!");
}
}
- src:
c.y.s.config.SecurityConfig
@Autowired
private CustomLoginSuccessHandler customLoginSuccessHandler;
@Autowired
private CustomLoginFailureHandler customLoginFailureHandler;
http.formLogin()
.loginPage("/api/user/login-routing")
.loginProcessingUrl("/login")
//.successForwardUrl("/api/user/main-routing")
.successHandler(customLoginSuccessHandler)
.failureHandler(customLoginFailureHandler)
.permitAll()
.and().logout()
.logoutUrl("/api/user/logout")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.permitAll();