一、文章简介
本文简要介绍了spring security的基本原理和实现,并基于springboot整合了spring security实现了基于数据库管理的用户的登录和登出,登录过程实现了验证码的校验功能。
二、spring security框架简介
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。主要包括:用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户能否访问该系统。用户认证过程一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作或访问某个页面。通常在一个企业级的系统中不同的用户所具有的权限也是不同的,简单的来说比如普通用户和管理员的区别,管理员显然具有更高的权限。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。spring security的主要核心功能为认证和授权,所有的架构也是基于这两个核心功能去实现的。
三、spring security原理
Spring security提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI,和AOP功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。Spring Security对Web安全性的支持大量地依赖于Servlet过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性。
四、spring boot整合spring security
4.1 准备工作
4.1.1数据库
4.1.2 pom.xml依赖
4.1.3 application.properties
1 spring.datasource.url=jdbc:mysql://localhost:3306/mydb?characterEncoding=utf8&useSSL=false&serverTimezone=GMT
2 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
3 spring.datasource.username=root
4 spring.datasource.password=
4.2 代码实现
4.2.1 t_user表的实体类SysUser的基本操作
实体类的基本增删改查可依据项目需要自行选择合适的ORM框架,此处我采用的是mybatis实现的基本用户查询操作
4.2.2 生成验证码的工具
验证码生产工具CaptchaController.java
/**
* 验证码操作处理
*
* @authors
*/
@RestController
public class CaptchaController
{
@Autowired
private RedisCache redisCache;
/**
* 生成验证码
*/
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) throws IOException
{
// 生成随机字串
String verifyCode = VerifyCodeUtils.generateVerifyCode(4);
// 唯一标识
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
redisCache.setCacheObject(verifyKey, verifyCode, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 生成图片
int w = 111, h = 36;
ByteArrayOutputStream stream = new ByteArrayOutputStream();
VerifyCodeUtils.outputImage(w, h, stream, verifyCode);
try
{
AjaxResult ajax = AjaxResult.success();
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(stream.toByteArray()));
return ajax;
}
catch (Exception e)
{
e.printStackTrace();
return AjaxResult.error(e.getMessage());
}
finally
{
stream.close();
}
}
}
4.2.3 自定义用户信息类CustomUserDetails 集成实体类TUser并实现security提供的UserDetails 接口
UserDetails是真正用于构建SpringSecurity登录的安全用户(UserDetails),也就是说,在springsecurity进行用户认证的过程中,是通过UserDetails的实现类去获取用户信息,然后进行授权验证的。不明白?没关系,继续往下看
复制代码
1 package com.shf.security.security.config;
2
3 import com.shf.security.user.entity.TUser;
4 import org.springframework.security.core.GrantedAuthority;
5 import org.springframework.security.core.userdetails.UserDetails;
6
7 import java.util.Collection;
8
9 /**
10 * 描述:自定义UserDetails,使UserDetails具有TUser的实体结构
11 *
12 * @Author shf
13 * @Date 2019/4/19 10:30
14 * @Version V1.0
15 **/
16 public class CustomUserDetails extends TUser implements UserDetails {
17 public CustomUserDetails(TUser tUser){
18 if(null != tUser){
19 this.setId(tUser.getId());
20 this.setCode(tUser.getCode());
21 this.setCreateTime(tUser.getCreateTime());
22 this.setUpdateTime(tUser.getUpdateTime());
23 this.setUsername(tUser.getUsername());
24 this.setPassword(tUser.getPassword());
25 this.setIsDelete(tUser.getIsDelete());
26 this.setEmail(tUser.getEmail());
27 this.setPhone(tUser.getPhone());
28 this.setRole(tUser.getRole());
29 }
30 }
31 @Override
32 public Collection extends GrantedAuthority> getAuthorities() {
33 return null;
34 }
35
36 @Override
37 public boolean isAccountNonExpired() {
38 return true;
39 }
40
41 @Override
42 public boolean isAccountNonLocked() {
43 return true;
44 }
45
46 @Override
47 public boolean isCredentialsNonExpired() {
48 return true;
49 }
50
51 @Override
52 public boolean isEnabled() {
53 return true;
54 }
55 }
复制代码
4.2.4 创建CustomUserDetailsService 类实现UserDetailsService接口
在下文将要提到的CustomAuthenticationProvider 类,也就是security核心的验证类中,会调用CustomUserDetailsService 中重写的loadUserByUsername方法
复制代码
1 package com.shf.security.security.config;
2
3 import com.shf.security.user.entity.TUser;
4 import com.shf.security.user.service.TUserService;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.security.core.userdetails.UserDetails;
7 import org.springframework.security.core.userdetails.UserDetailsService;
8 import org.springframework.security.core.userdetails.UsernameNotFoundException;
9 import org.springframework.stereotype.Component;
10
11 /**
12 * 描述:自定义UserDetailsService,从数据库读取用户信息,实现登录验证
13 *
14 * @Author shf
15 * @Date 2019/4/21 17:21
16 * @Version V1.0
17 **/
18 @Component
19 public class CustomUserDetailsService implements UserDetailsService {
20 @Autowired
21 private TUserService userService;
22
23 /**
24 * 认证过程中 - 根据登录信息获取用户详细信息
25 *
26 * @param username 登录用户输入的用户名
27 * @return
28 * @throws UsernameNotFoundException
29 */
30 @Override
31 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
32 //根据用户输入的用户信息,查询数据库中已注册用户信息
33 TUser user = userService.findByName(username);
34 //如果用户不存在直接抛出UsernameNotFoundException异常
35 if (user == null) throw new UsernameNotFoundException("用户名为" + username + "的用户不存在");
36 return new CustomUserDetails(user);
37 }
38 }
复制代码
4.2.5 新建类CustomWebAuthenticationDetails继承WebAuthenticationDetails类
类似于UserDetails类给我们提供了用户详细信息一样,WebAuthenticationDetails则为我们提供了登录请求的用户的信息(也就是申请登录的用户的username和password信息),springsecurity默认只验证用户的username和password信息,所以我们如果想实现验证码登录,需要重写WebAuthenticationDetails类,使其能通过HttpServletRequest获取到用户输入的验证码的信息。
复制代码
1 package com.shf.security.security.config;
2
3 import org.springframework.security.web.authentication.WebAuthenticationDetails;
4
5 import javax.servlet.http.HttpServletRequest;
6
7 /**
8 * 描述:自定义WebAuthenticationDetails,将验证码和用户名、密码一同带入AuthenticationProvider中
9 *
10 * @Author shf
11 * @Date 2019/4/21 16:58
12 * @Version V1.0
13 **/
14 public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {
15 private static final long serialVersionUID = 6975601077710753878L;
16 private final String verifyCode;
17 public CustomWebAuthenticationDetails(HttpServletRequest request) {
18 super(request);
19 verifyCode = request.getParameter("verifyCode");
20 }
21
22 public String getVerifyCode() {
23 return verifyCode;
24 }
25
26 @Override
27 public String toString() {
28 StringBuilder sb = new StringBuilder();
29 sb.append(super.toString()).append("; verifyCode: ").append(this.getVerifyCode());
30 return sb.toString();
31 }
32 }
复制代码
4.2.6 创建CustomAuthenticationDetailsSource类继承AuthenticationDetailsSource类
上面提到CustomWebAuthenticationDetails 需要通过HttpServletRequest获取到用户输入的验证码的信息。AuthenticationDetailsSource类就是初始化CustomWebAuthenticationDetails类的地方,在这里面我们需要将HttpServletRequest传递到CustomAuthenticationDetailsSource中。
复制代码
1 package com.shf.security.security.config;
2
3 import org.springframework.security.authentication.AuthenticationDetailsSource;
4 import org.springframework.security.web.authentication.WebAuthenticationDetails;
5 import org.springframework.stereotype.Component;
6
7 import javax.servlet.http.HttpServletRequest;
8
9 /**
10 * 描述:自定义AuthenticationDetailsSource,将HttpServletRequest注入到CustomWebAuthenticationDetails,使其能获取到请求中的验证码等其他信息
11 *
12 * @Author shf
13 * @Date 2019/4/21 17:03
14 * @Version V1.0
15 **/
16 @Component
17 public class CustomAuthenticationDetailsSource implements AuthenticationDetailsSource {
18 @Override
19 public WebAuthenticationDetails buildDetails(HttpServletRequest request) {
20 return new CustomWebAuthenticationDetails(request);
21 }
22 }
复制代码
4.2.7 实现自定义认证器(重点),创建CustomAuthenticationProvider继承AbstractUserDetailsAuthenticationProvider类
AbstractUserDetailsAuthenticationProvider类实现的是AuthenticationProvider接口
复制代码
1 package com.shf.security.security.config;
2
3 import com.shf.security.utils.VerifyCodeUtil;
4 import lombok.extern.slf4j.Slf4j;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.security.authentication.BadCredentialsException;
7 import org.springframework.security.authentication.DisabledException;
8 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
9 import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
10 import org.springframework.security.core.Authentication;
11 import org.springframework.security.core.AuthenticationException;
12 import org.springframework.security.core.userdetails.UserDetails;
13 import org.springframework.stereotype.Component;
14 import org.springframework.web.context.request.RequestContextHolder;
15 import org.springframework.web.context.request.ServletRequestAttributes;
16
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpSession;
19
20 /**
21 * 描述:自定义SpringSecurity的认证器
22 *
23 * @Author shf
24 * @Date 2019/4/21 17:30
25 * @Version V1.0
26 **/
27 @Component
28 @Slf4j
29 public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {//implements AuthenticationProvider {
30 @Autowired
31 private CustomUserDetailsService userDetailsService;
32
33 @Override
34 protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
35
36 }
37
38 @Override
39 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
40 //用户输入的用户名
41 String username = authentication.getName();
42 //用户输入的密码
43 String password = authentication.getCredentials().toString();
44 //通过CustomWebAuthenticationDetails获取用户输入的验证码信息
45 CustomWebAuthenticationDetails details = (CustomWebAuthenticationDetails) authentication.getDetails();
46 String verifyCode = details.getVerifyCode();
47 if(null == verifyCode || verifyCode.isEmpty()){
48 log.warn("未输入验证码");
49 throw new NullPointerException("请输入验证码");
50 }
51 //校验验证码
52 if(!validateVerifyCode(verifyCode)){
53 log.warn("验证码输入错误");
54 throw new DisabledException("验证码输入错误");
55 }
56 //通过自定义的CustomUserDetailsService,以用户输入的用户名查询用户信息
57 CustomUserDetails userDetails = (CustomUserDetails) userDetailsService.loadUserByUsername(username);
58 //校验用户密码
59 if(!userDetails.getPassword().equals(password)){
60 log.warn("密码错误");
61 throw new BadCredentialsException("密码错误");
62 }
63 Object principalToReturn = userDetails;
64 //将用户信息塞到SecurityContext中,方便获取当前用户信息
65 return this.createSuccessAuthentication(principalToReturn, authentication, userDetails);
66 }
67
68 @Override
69 protected UserDetails retrieveUser(String s, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
70 return null;
71 }
72
73 /**
74 * 验证用户输入的验证码
75 * @param inputVerifyCode
76 * @return
77 */
78 public boolean validateVerifyCode(String inputVerifyCode){
79 //获取当前线程绑定的request对象
80 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
81 // 这个VerifyCodeFactory.SESSION_KEY是在servlet中存入session的名字
82 HttpSession session = request.getSession();
83 String verifyCode = (String)session.getAttribute(VerifyCodeUtil.SESSION_KEY);
84 if(null == verifyCode || verifyCode.isEmpty()){
85 log.warn("验证码过期请重新验证");
86 throw new DisabledException("验证码过期,请重新验证");
87 }
88 // 不分区大小写
89 verifyCode = verifyCode.toLowerCase();
90 inputVerifyCode = inputVerifyCode.toLowerCase();
91
92 log.info("验证码:{}, 用户输入:{}", verifyCode, inputVerifyCode);
93
94 return verifyCode.equals(inputVerifyCode);
95 }
96
97 @Override
98 public boolean supports(Class> authentication) {
99 return authentication.equals(UsernamePasswordAuthenticationToken.class);
100 }
101 }
复制代码
如上图所示,AuthenticationProvider接口为我们提供了security核心的认证方法authenticate方法,该方法就是实现用户认证的方法。我们自定义实现authenticate方法,大致思路如下,通过CustomWebAuthenticationDetails获取到用户输入的username,password,verifyCode信息。通过CustomUserDetails 中获取用户信息(数据库中注册的用户的信息),然后对用户信息进行比对认证。最终实现认证过程。
当然,也可以直接实现AuthenticationProvider 接口,然后实现authenticate方法。这都是可以的但是有现成的AbstractUserDetailsAuthenticationProvider可用,为啥还要再写一遍呢?尤其是AbstractUserDetailsAuthenticationProvider类提供的createSuccessAuthentication方法,封装了一个完美的Authentication(后续会继续提到)。AuthenticationProvider 的supports方法呢是直接决定哪一个AuthenticationProvider 的实现类是我们需要的认证器。
4.2.8 创建WebSecurityConfig 继承WebSecurityConfigurerAdapter配置类。(spring security的配置类)
具体看代码注释吧,很详细的。
值得一提的是第81行的配置,是我们实现ajax登录的关键。也就是实现前后端分离项目的关键代码
复制代码
1 package com.shf.security.security.config;
2
3 import lombok.extern.slf4j.Slf4j;
4 import org.springframework.beans.factory.annotation.Autowired;
5 import org.springframework.context.annotation.Bean;
6 import org.springframework.context.annotation.Configuration;
7 import org.springframework.security.authentication.AuthenticationDetailsSource;
8 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
9 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
10 import org.springframework.security.config.annotation.web.builders.WebSecurity;
11 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
12 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
13 import org.springframework.security.core.Authentication;
14 import org.springframework.security.core.AuthenticationException;
15 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
16 import org.springframework.security.crypto.password.PasswordEncoder;
17 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
18 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
19 import org.springframework.security.web.authentication.WebAuthenticationDetails;
20 import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
21
22 import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpServletResponse;
25 import java.io.IOException;
26 import java.io.PrintWriter;
27
28 /**
29 * 描述:
30 *
31 * @Author shf
32 * @Date 2019/4/19 10:54
33 * @Version V1.0
34 **/
35 @Configuration
36 @EnableWebSecurity
37 @Slf4j
38 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
39 @Autowired
40 private CustomAuthenticationProvider customAuthenticationProvider;
41
42 @Autowired
43 private CustomUserDetailsService customUserDetailsService;
44
45 @Autowired
46 private AuthenticationDetailsSource authenticationDetailsSource;
47
48 @Override
49 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
50 //将自定义的CustomAuthenticationProvider装配到AuthenticationManagerBuilder
51 auth.authenticationProvider(customAuthenticationProvider);
52 //将自定的CustomUserDetailsService装配到AuthenticationManagerBuilder
53 auth.userDetailsService(customUserDetailsService).passwordEncoder(new PasswordEncoder() {
54 @Override
55 public String encode(CharSequence charSequence) {
56 return charSequence.toString();
57 }
58
59 @Override
60 public boolean matches(CharSequence charSequence, String s) {
61 return s.equals(charSequence.toString());
62 }
63 });
64 }
65 @Override
66 public void configure(HttpSecurity http) throws Exception {
67 http
68 .cors()
69 .and().csrf().disable();//开启跨域
70 http /*匿名请求:不需要进行登录拦截的url*/
71 .authorizeRequests()
72 .antMatchers("/getVerifyCode").permitAll()
73 .anyRequest().authenticated()//其他的路径都是登录后才可访问
74 .and()
75 /*登录配置*/
76 .formLogin()
77 .loginPage("/login_page")//登录页,当未登录时会重定向到该页面
78 .successHandler(authenticationSuccessHandler())//登录成功处理
79 .failureHandler(authenticationFailureHandler())//登录失败处理
80 .authenticationDetailsSource(authenticationDetailsSource)//自定义验证逻辑,增加验证码信息
81 .loginProcessingUrl("/login")//restful登录请求地址
82 .usernameParameter("username")//默认的用户名参数
83 .passwordParameter("password")//默认的密码参数
84 .permitAll()
85 .and()
86 /*登出配置*/
87 .logout()
88 .permitAll()
89 .logoutSuccessHandler(logoutSuccessHandler());
90 }
91
92 /**
93 * security检验忽略的请求,比如静态资源不需要登录的可在本处配置
94 * @param web
95 */
96 @Override
97 public void configure(WebSecurity web){
98 // platform.ignoring().antMatchers("/");
99 }
100
101 @Autowired
102 public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
103 auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
104 auth.eraseCredentials(false);
105 }
106 //密码加密配置
107 @Bean
108 public BCryptPasswordEncoder passwordEncoder() {
109 return new BCryptPasswordEncoder(4);
110 }
111 //登入成功
112 @Bean
113 public AuthenticationSuccessHandler authenticationSuccessHandler() {
114 return new AuthenticationSuccessHandler() {
115 /**
116 * 处理登入成功的请求
117 *
118 * @param httpServletRequest
119 * @param httpServletResponse
120 * @param authentication
121 * @throws IOException
122 * @throws ServletException
123 */
124 @Override
125 public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
126 httpServletResponse.setContentType("application/json;charset=utf-8");
127 PrintWriter out = httpServletResponse.getWriter();
128 out.write("{\"status\":\"success\",\"msg\":\"登录成功\"}");
129 out.flush();
130 out.close();
131 }
132 };
133 }
134 //登录失败
135 @Bean
136 public AuthenticationFailureHandler authenticationFailureHandler(){
137 return new AuthenticationFailureHandler() {
138 /**
139 * 处理登录失败的请求
140 * @param httpServletRequest
141 * @param httpServletResponse
142 * @param e
143 * @throws IOException
144 * @throws ServletException
145 */
146 @Override
147 public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
148 httpServletResponse.setContentType("application/json;charset=utf-8");
149 PrintWriter out = httpServletResponse.getWriter();
150 out.write("{\"status\":\"error\",\"msg\":\"登录失败\"}");
151 out.flush();
152 out.close();
153 }
154 };
155 }
156 //登出处理
157 @Bean
158 public LogoutSuccessHandler logoutSuccessHandler() {
159 return new LogoutSuccessHandler() {
160 /**
161 * 处理登出成功的请求
162 *
163 * @param httpServletRequest
164 * @param httpServletResponse
165 * @param authentication
166 * @throws IOException
167 * @throws ServletException
168 */
169 @Override
170 public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
171 httpServletResponse.setContentType("application/json;charset=utf-8");
172 PrintWriter out = httpServletResponse.getWriter();
173 out.write("{\"status\":\"success\",\"msg\":\"登出成功\"}");
174 out.flush();
175 out.close();
176 }
177 };
178 }
179 }
复制代码
4.2.9 LoginController
View Code
4.2.10 UserHolder 工具类
在日常的业务中,在很多业务代码中,我们都需要获取当前用户的信息。这个类就是一个静态工具类。
复制代码
1 package com.shf.security.utils;
2
3 import com.shf.security.user.entity.TUser;
4 import org.springframework.security.core.Authentication;
5 import org.springframework.security.core.context.SecurityContext;
6 import org.springframework.security.core.context.SecurityContextHolder;
7
8 /**
9 * 描述:
10 *
11 * @Author shf
12 * @Description TODO
13 * @Date 2019/4/21 15:24
14 * @Version V1.0
15 **/
16 public class UserHolder {
17 public static TUser getUserDetail(){
18 SecurityContext ctx = SecurityContextHolder.getContext();
19 Authentication auth = ctx.getAuthentication();
20 TUser user = (TUser) auth.getPrincipal();
21 return user;
22 }
23 public static String getUserCode(){
24 SecurityContext ctx = SecurityContextHolder.getContext();
25 Authentication auth = ctx.getAuthentication();
26 TUser user = (TUser) auth.getPrincipal();
27 return user.getCode();
28 }
29 public static int getUserId(){
30 SecurityContext ctx = SecurityContextHolder.getContext();
31 Authentication auth = ctx.getAuthentication();
32 TUser user = (TUser) auth.getPrincipal();
33 return user.getId();
34 }
35 }
复制代码
4.2.10 其他工具类Response.java
View Code
五、问题总结
5.1 验证码问题
其实呢通过第二部分对security原理的分析,我们不难看出,spring security就是建立在一连串的过滤器filter上的,spring security通过这些过滤器逐层对请求进行过滤,然后进行各种登录认证和授权过程。说道这里估计大家也就能想到另外的实现验证码验证登录的方式。也就是在认证用户输入的用户名和密码之前验证验证码信息。UsernamePasswordAuthenticationFilter过滤器顾名思义就是用户名和密码的过滤器。所以我们只需要在4.2.8 章节中的WebSecurityConfig中addFilterBefore()配置在UsernamePasswordAuthenticationFilter过滤器之前执行VerifyCodeFilter过滤器。然后在VerifyCodeFilter过滤器中执行验证码的验证逻辑即可。
1 .and()
2 .addFilterBefore(new VerifyCodeFilter(),UsernamePasswordAuthenticationFilter.class)
但是这种方式呢有一种天然的缺点,也就是没法办将除username和password的信息带到认证器中进行统一认证。而且如果我们除了验证码意外还需要验证更多的信息的话。岂不是要写n多个filter。
5.2 貌似忘了进行测试登录
浏览器请求:http://localhost:8080/user/test
结果:
正是我们想要的结果。
登录验证还是使用postman吧,因为spring security默认只处理post方式的登录请求。浏览器提交restful请求默认是get的。所以。。。
postman请求验证码
postman登录