SpringSecurity简介
SpringSecurity是Spring安全框架中的一员,在SpringBoot出现之前,SpringSecurity已经发展了许久了,但使用并不多,这个领域一直都是Shiro的天下。
相对于Shiro,在SSM/SSH中整合SpringSecurity都是比较麻烦的,所以即使SpringSecurity功能比Shiro强大,但使用反而没Shiro多(虽然Shiro功能没用SpringSecurity多,但绝大部分项目而言,已经够用了)。
SpringBoot出来以后,对SpringSecurity提供了自动化配置方案,可以零配置使用SpringSecurity。
所以常见的技术栈组合是如下:
- SSM + Shiro
- SpringBoot + SpringSecurity
回到SpringSecurity,它的核心实现就是维护了一组过滤器链。
基本使用
创建项目,引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--可选-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
随意编写Controller
@Controller
public class RouterController {
@GetMapping({"/","/index"})
public String index(){
return "index";
}
@GetMapping("/tologin")
public String tologin(){
return "views/login";
}
@GetMapping("/level1/{id}")
public String level1(@PathVariable("id") int id){
return "views/level1/"+id;
}
@GetMapping("/level2/{id}")
public String level2(@PathVariable("id") int id){
return "views/level2/"+id;
}
@GetMapping("/level3/{id}")
public String level3(@PathVariable("id") int id){
return "views/level3/"+id;
}
}
默认情况下,引入了SpringSecurity,所有的页面都需要进行认证
编写配置类,修改默认配置
//@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//这里可以配置一些忽略拦截项 当然也可以下面走匿名访问 不建议
@Override
public void configure(WebSecurity web) throws Exception {
//静态资源过滤
web.ignoring().antMatchers("/resources/**");
//过滤某个路由
web.ignoring().antMatchers("/vercode");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//为不同的访问路径配置不同的权限
.antMatchers("/level1/**").hasAnyRole("vip1","vip2","vip3")
.antMatchers("/level2/**").hasAnyRole("vip2","vip3")
.antMatchers("/level3/**").hasAnyRole("vip3")
//剩下的路径设置为所有人皆可访问
.anyRequest().permitAll();
//配置登录相关
http.formLogin()
//登录页所在路由 默认/login
.loginPage("/tologin")
//登录表单发送目标的地址 如果不配置 默认是你的登录页所在路由
.loginProcessingUrl("/login")
//表单中参数的名称 默认username password
.usernameParameter("name")
.passwordParameter("pwd")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//判断请求是异步(返回JSON)还是同步(返回页面)
String xRequestedWith = request.getHeader("x-requested-with");//通过请求头判断
if("XMLHttpRequest".equals(xRequestedWith)){//异步返回
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(403,"你还没有登录"));
}else {//同步返回
response.sendRedirect(request.getContextPath()+"/login");
}
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//同上
}
})
//退出登录相关配置 差不多和上面一样 源码中有简易的教学Demo
http.logout();
//记住我功能,如果你使用的是自定义登录页面,需要自己写选择框
http.rememberMe()
//表单中参数的名称
.rememberMeParameter("remember");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//添加账户,向内存中,一般用来测试用的,实际开发一般不这么玩
//BCryptPasswordEncoder是SpringSecurity提供的密码编码工具,可以非常方便的时间密码的加密和加盐,相同明文加密的密码完全不同
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("saber").password(new BCryptPasswordEncoder().encode("123")).roles("vip1")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2","vip3");
}
//也可以通过配置文件配置
//spring.security.user.name=javaboy
//spring.security.user.password=123
}
到此为止,一个大体的使用框架就搭建完成了,可以进行测试了
更多使用
登录成功和失败处理
http.formLogin()
.loginPage("/tologin")
.loginProcessingUrl("/login")
.usernameParameter("name")
.passwordParameter("pwd")
//登录成功于失败的跳转路由
.successForwardUrl("/success")
.failureForwardUrl("/failure");
一般上面这种做法无法满足我们的需求,而且查看源码可知,底层就是一个forward跳转,我们知道forward跳转是无法跳到应用之外的页面的,由于这些功能往往会有比较复杂的逻辑,所以SpringSecurity给我们提供了.successHandler()
方法去自己实现一个成功跳转逻辑,需要给他一个实现了AuthenticationSuccessHandler
接口的类,它会去执行里面的onAuthenticationSuccess()
方法
http.formLogin()
.loginPage("/tologin")
.loginProcessingUrl("/login")
.usernameParameter("name")
.passwordParameter("pwd")
//也可以使用简单的路径跳转,看需求 .successForwardUrl()
.successHandler(new AuthenticationSuccessHandler() {
//这里直接在这里实例化一个接口,也可以先创建一个类继承接口在实现,然后通过new的形式或注入的形式在这里使用
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//判断请求是异步(返回JSON)还是同步(返回页面)
String xRequestedWith = httpServletRequest.getHeader("x-requested-with");//通过请求头判断
if("XMLHttpRequest".equals(xRequestedWith)){//异步返回
httpServletResponse.setContentType("application/plain;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.write(CommunityUtil.getJSONString(403,"你还没有登录"));
}else {//同步返回
httpServletResponse.sendRedirect(httpServletRequest.getContextPath()+"/login");
}
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
}
});
上面采用了最简单的写法,可读性差,建议还是自己去继承并实现,然后通过new的方式或注入的方式提供给.successHandler()
权限不足处理
//权限不够时
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint() {
//没有登录时处理
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//判断请求是异步(返回JSON)还是同步(返回页面)
String xRequestedWith = request.getHeader("x-requested-with");//通过请求头判断
if("XMLHttpRequest".equals(xRequestedWith)){//异步返回
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(403,"你还没有登录"));
}else {//同步返回
response.sendRedirect(request.getContextPath()+"/login");
}
}
})
.accessDeniedHandler(new AccessDeniedHandler() {
//权限不足时处理
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
//判断请求是异步(返回JSON)还是同步(返回页面)
String xRequestedWith = request.getHeader("x-requested-with");//通过请求头判断
if("XMLHttpRequest".equals(xRequestedWith)){//异步返回
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(403,"权限不足"));
}else {//同步返回
response.sendRedirect(request.getContextPath()+"/denied");
}
}
});
自定义登录逻辑
自定义登录逻辑需要创建一个实现UserDetailsService
接口的类,并把它注入到IOC容器中
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Bean
private BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
com.example.security.entity.User u = userMapper.findUserByName(s);
System.out.println(u.toString());
if (u == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
//比较密码,匹配成功会返回UserDetails,实际上也会去数据库查
String password = passwordEncoder.encode(u.getPassword());
//用于添加用户的权限。只要把用户权限添加到authorities。
List<GrantedAuthority> authorities = new ArrayList<>();
Role role = roleMapper.findRoleById((int) u.getRoleId());
if (role != null) {
//使用Role和Authority都是这一套代码
//这里有给大坑 如果你使用Role进行授权的话,一定要如下加上 ROLE_
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName()));
}
User user = new User(u.getUsername(), password, authorities);
return user;
}
}
然后在配置类中添加如下(不是必须的,建议写上):
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
认证授权注解
在使用注解前需要在配置类或启动类上添加@EnableGlobalMethodSecurity
表示开启注解认证授权
@Secured
需要在上面添加的注解中添加一个参数@EnableGlobalMethodSecurity(securedEnabled = true)
,开启这个注解的使用
用户具有某个Role可以访问,在Controller中使用
@GetMapping("/level1")
@Secured({"ROLE_vip1","ROLE_vip2","ROLE_vip3"})
public String level1() {
return "level1";
}
@GetMapping("/level2")
@Secured({"ROLE_vip2","ROLE_vip3"})
public String level2() {
return "level2";
}
@GetMapping("/level3")
@Secured({"ROLE_vip3"})
public String level3() {
return "level3";
}
@PreAuthorize
需要在上面添加的注解中添加一个参数@EnableGlobalMethodSecurity(prePostEnabled = true)
,开启这个注解的使用
进入方法执行之前进行验证
@GetMapping("/level3")
//@PreAuthorize("hasRole('ROLE_xxx')") 这里直接使用上面介绍过的方法名称 就根调用方法一样
@PreAuthorize("hasAnyAuthority('admin3')")
public String level3() {
return "level3";
}
@PostAuthorize
需要在上面添加的注解中添加一个参数@EnableGlobalMethodSecurity(prePostEnabled = true)
,开启这个注解的使用
在方法执行之后判断是否有权限
@GetMapping("/level3")
@PostAuthorize("hasAnyAuthority('admin3')")
public String level3() {
System.out.println("fwcg");
return "level3";
}
@PreFilter / @PostFilter
对传入 / 传出的数据进行过滤 Spring Security将移除使对应表达式的结果为false的元素。
@PostFilter("filterObject.id%2==0")
@GetMapping("/getuser")
public List<User> findAll() {
List<User> userList = new ArrayList<User>();
User user;
for (int i = 0; i < 10; i++){
user = new User();
user.setId(i);
userList.add(user);
}
System.out.println(userList);
return userList;
}
上述代码表示将对返回结果中id不为偶数的user进行移除。filterObject是使用@PreFilter和@PostFilter时的一个内置表达式,表示集合中的当前对象
当@PreFilter标注的方法拥有多个集合类型的参数时,需要通过@PreFilter的filterTarget属性指定当前@PreFilter是针对哪个参数进行过滤的。
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> usernames) {
...
}
记住我
最简单的实现方式是储存在内存中,在配置类中添加如下代码即可
http.rememberMe();//实现记住我自动登录,核心的代码只有这这一行
如果你使用默认登录页,什么也不用做,但如果你是自定义的,请在页面中添加复选框,
<label> <input type="checkbox" name="remember-me" />记住我</label>
前端传值时SpringSecurity将读取键: remember-me,只能叫这个名,Ajax传递参数也必须为 remember-me,但后端可以通过.rememberMeParameter("xxx")
修改参数名称,还可以通过.rememberMeCookieName()
修改Cookie中Key的名称
.tokenValiditySeconds(60*2)
这个可以修改有效时长默认2周,单位秒
http.rememberMe()
.rememberMeParameter("rm")
.rememberMeCookieName("rm-cookie")
.tokenValiditySeconds(60*2);
基于数据库的实现
全部操作在配置类中
先注入数据源
@Autowired
private DataSource dataSource;
注入TokenRepostory组件
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);//使用数据源
jdbcTokenRepository.setCreateTableOnStartup(true);//创建数据表,第一次运行的时候使用,以后注释吊,不然报错
return jdbcTokenRepository;
}
配置
http.rememberMe()
.rememberMeParameter("rm")
.rememberMeCookieName("rm-cookie")
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60*2);
//.userDetailsService(userDetailsService);//如果上面自定义登录中直接指示了auth.userDetailsService(userDetailsService);这里可以不写
这样即可
整合JWT
导入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
创建JWT工具类
管理Token相关的操作
/**
* jwt 工具类 主要是生成token 检查token等相关方法
*/
public class JwtUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
// TOKEN 过期时间
public static final long EXPIRATION = 1000 * 60 * 30; // 三十分钟
public static final String APP_SECRET_KEY = "secret";
private static final String ROLE_CLAIMS = "rol";
/**
* 生成token
*
* @param username
* @param roles
* @return
*/
public static String createToken(String username, List<String> roles) {
Map<String, Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, roles);
String token = Jwts
.builder()
.setSubject(username)
.setClaims(map)
.claim("username", username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, APP_SECRET_KEY).compact();
return token;
}
/**
* 获取当前登录用户用户名
*
* @param token
* @return
*/
public static String getUsername(String token) {
Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
return claims.get("username").toString();
}
/**
* 获取当前登录用户角色
*
* @param token
* @return
*/
public static ArrayList<String> getUserRole(String token) {
Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
ArrayList<String> rols = claims.get("rol", ArrayList.class);
return rols;
}
/**
* 获解析token中的信息
*
* @param token
* @return
*/
public static Claims checkJWT(String token) {
try {
final Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
return claims;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 检查token是否过期
*
* @param token
* @return
*/
public static boolean isExpiration(String token) {
Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
return claims.getExpiration().before(new Date());
}
}
创建JwtUser类
主要用于封装登录用户相关信息,例如用户名,密码,权限集合等,必须实现UserDetails接口
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
public class JwtUser implements UserDetails {
private Integer id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public JwtUser() {
}
// 写一个能直接使用user创建jwtUser的构造器
public JwtUser(User user, Collection<? extends GrantedAuthority> authorities) {
id = Math.toIntExact(user.getId());
username = user.getUsername();
password = user.getPassword();
this.authorities = authorities;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean isAccountNonExpired() {
return true;
}
public boolean isAccountNonLocked() {
return true;
}
public boolean isCredentialsNonExpired() {
return true;
}
public boolean isEnabled() {
return true;
}
}
创建JwtUserService
类似于自定义登录逻辑,必须实现UserDetailsService
@Service
public class JwtUserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Bean
private BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 根据前端传入的用户信息 去数据库查询是否存在该用户
*
* @param s
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = this.userMapper.findUserByName(s);
user.setPassword(passwordEncoder.encode(user.getPassword()));
//用于添加用户的权限。只要把用户权限添加到authorities。
List<GrantedAuthority> authorities = new ArrayList<>();
Role role = roleMapper.findRoleById((int) user.getRoleId());
if (role != null) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName()));
}
if (user != null) {
JwtUser jwtUser = new JwtUser(user, authorities);
return jwtUser;
} else {
try {
throw new ValidationException("该用户不存在");
} catch (ValidationException e) {
e.printStackTrace();
}
}
return null;
}
}
自定义用户登录拦截器
/**
* 验证用户名密码正确后,生成一个token,并将token返回给客户端
* 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法 ,
* attemptAuthentication:接收并解析用户凭证。
* successfulAuthentication:用户成功登录后,这个方法会被调用,我们在这个方法里生成token并返回。
*/
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
/**
* security拦截默认是以POST形式走/login请求,我们这边设置为走/token请求
*
* @param authenticationManager
*/
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/token");
}
/**
* 接收并解析用户凭证
*
* @param request
* @param response
* @return
* @throws AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 从输入流中获取到登录的信息
try {
User loginUser = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword())
);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 成功验证后调用的方法
// 如果验证成功,就生成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
System.out.println("jwtUser:" + jwtUser.toString());
List<String> roles = new ArrayList<>();
Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
for (GrantedAuthority authority : authorities) {
roles.add(authority.getAuthority());
}
String token = JwtUtils.createToken(jwtUser.getUsername(), roles);
// 返回创建成功的token 但是这里创建的token只是单纯的token
// 按照jwt的规定,最后请求的时候应该是 `Bearer token`
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
String tokenStr = JwtUtils.TOKEN_PREFIX + token;
response.setHeader("token", tokenStr);
}
// 失败 返回错误就行
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.getWriter().write("authentication failed, reason: " + failed.getMessage());
}
}
自定义权限拦截器
假如admin登录成功后,携带token去请求其他接口时,该拦截器会判断权限是否正确
/**
* 登录成功之后走此类进行 鉴定 权限
*/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String tokenHeader = request.getHeader(JwtUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token,则进行解析,并且设置认证信息
try {
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
} catch (Exception e) {
e.printStackTrace();
}
super.doFilterInternal(request, response, chain);
}
// 这里从token中获取用户信息并新建一个token 就是上面说的设置认证信息
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws Exception {
String token = tokenHeader.replace(JwtUtils.TOKEN_PREFIX, "");
// 检测token是否过期 如果过期会自动抛出错误
JwtUtils.isExpiration(token);
String username = JwtUtils.getUsername(token);
ArrayList<String> roles = JwtUtils.getUserRole(token);
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (roles != null) {
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
}
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
return null;
}
}
SpringSecurity配置
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JwtUserService jwtUserService;
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/level1/**").authenticated()//.hasAnyRole("vip1","vip2","vip3")
.antMatchers("/level2/**").authenticated()//.hasAnyRole("vip2","vip3")
.antMatchers("/level3/**").authenticated()//.hasAnyRole("vip3")
.anyRequest().permitAll();
http.addFilter(new JWTAuthenticationFilter(authenticationManager())) // 用户登录拦截
.addFilter(new JWTAuthorizationFilter(authenticationManager())) // 权限拦截
// 不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling();
http.formLogin();
http.csrf().disable();
http.logout().logoutUrl("/logout").logoutSuccessUrl("/getuser");
http.rememberMe()
.rememberMeParameter("rm")
.rememberMeCookieName("rm-cookie")
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60*2);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserService);
}
}
参考:
官网:https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/#servlet-applications
视频:https://www.bilibili.com/video/BV15a411A7kP
参考文章1:https://www.cnblogs.com/lenve/p/11242055.html
参考文章2:https://www.jianshu.com/p/7817e372c1db
参考文章3:https://blog.csdn.net/qq_42640067/article/details/113062222
参考文章4:https://blog.csdn.net/qq_22172133/article/details/86503223
JWT参考:https://blog.csdn.net/weixin_45452416/article/details/109528425