本文需要有一定的Spring Security基础,最好是了解其基本体系结构以及核心组件,如果可以简单配置并运行该框架最好。为确保阅读本文时更加易于理解,可以优先阅读以下文章:
Mybatis的引入:从头开始搭建一个SpringBoot项目-整合MyBatis
日志记录模块:从头开始搭建一个SpringBoot项目-日志记录logback
前后端交互文档:从头开始搭建一个SpringBoot项目–Swagger2的配置
本文是在上述框架引入之后,再进行的Spring Security引入和配置。
与本文相关的是:
Token基本理解及工具类 - Java生成token的工具类(对称签名)
Spring Security的体系结构 – SpringSecurity官网的Architecture部分的翻译
Spring Security核心组件 – SpringSecurity官网的Servlet Authentication Architecture部分的翻译
Spring Security认证流程分析 – SpringSecurity身份认证流程分析
在后文中的相关部分中,你将还会看到上述文章的引用
阅读本文你将学会以下内容:
Spring Security进行身份验证token验证在Spring Security中的使用Spring Security自定义异常处理Spring Security自定义过滤器这里的SpringBoot版本的信息在我之前的文章里: 从头开始搭建一个SpringBoot项目-整合MyBatis有说过,SpringBoot的版本是2.7.4。
org.springframework.boot spring-boot-starter-security io.jsonwebtoken jjwt 0.9.1
com.alibaba fastjson 1.2.72 org.springframework.boot spring-boot-starter-data-redis 2.5.4
文章的末尾会又具体的yml文件的信息,这里只是提一下
spring:redis:host: localhostport: 6379
至于使用Redis的原因,大家可以看看我之前的文章: Java生成token的工具类(对称签名)
这里就不再使用Session存储用户信息了,转为使用Redis。安装和在SpringBoot里的配置就不在这里赘述了,大家自行寻找教程配置安装即可。
在调用接口的时候,大都返回的是code – 状态码、msg – 提示信息、data--数据,那么我们可以给它封装一下。

接口通用的的返回
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
public class Result {//状态码private Integer code;//提示信息private String msg;//返回的数据private T data;
}
返回的枚举类
@Getter
public enum ResultCode {ERROR(-1,"未知错误"),SUCCESS(10000,"操作成功"),LOGINSUCCESS(500,"登录成功。"),UORPWRONG(4000,"用户名或密码错误");private final Integer code;private final String msg;ResultCode(Integer code , String msg) {this.code = code;this.msg = msg;}
}
用于获取返回实体
public class ResultUtil {public static Result success(Object object){Result result = new Result();result.setCode(ResultCode.SUCCESS.getCode());result.setMsg(ResultCode.SUCCESS.getMsg());result.setData(object);return result;}public static Result success(ResultCode resultCode , Object object){Result result = new Result();result.setCode(resultCode.getCode());result.setMsg(resultCode.getMsg());result.setData(object);return result;}public static Result success(ResultCode resultCode){Result result = new Result();result.setCode(resultCode.getCode());result.setMsg(resultCode.getMsg());return result;}public static Result success(){Result result = new Result();result.setCode(ResultCode.SUCCESS.getCode());result.setMsg(ResultCode.SUCCESS.getMsg());return success(null);}public static Result error(Integer code,String msg){Result result = new Result();result.setCode(code);result.setMsg(msg);return result;}public static Result error(){Result result = new Result();result.setCode(ResultCode.ERROR.getCode());result.setMsg(ResultCode.ERROR.getMsg());return result;}public static Result error(ResultCode resultCode){Result result = new Result();result.setCode(resultCode.getCode());result.setMsg(resultCode.getMsg());return result;}public static Result error(String msg){Result result = new Result();result.setCode(ResultCode.ERROR.getCode());result.setMsg(msg);return result;}public static Result error(ResultCode resultCode , Object object){Result result = new Result();result.setCode(resultCode.getCode());result.setMsg(resultCode.getMsg());result.setData(object);return result;}
}

DruidConfig和Swagger2Config在以下的文章里讲到过,这里就不再赘述了。
从头开始搭建一个SpringBoot项目-整合MyBatis
从头开始搭建一个SpringBoot项目–Swagger2的配置
在低版本中Spring Security的配置文件通常是通过继承一个WebSecurityConfigurerAdapter的父类,来Spring Security文件的配置
如以下这样
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//构建认证管理器的配置}@Overrideprotected void configure(HttpSecurity http) throws Exception {//框架主要的配置}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {//认证管理器的配置}
}
但是在一些高版本的Spring Security已经不再使用上述方法,而是使用@Bean注解的方式来配置,如以下这样
Spring Security的配置信息放在这里。以bean的形式配置
@Configuration
public class SecurityConfig {//权限异常的处理 多指访问非自己权限内的信息 本文搭建的并未用到@AutowiredCustomAccessDeniedHandler customAccessDeniedHandler;//认证失败的错误处理 密码错误 token异常等@AutowiredCustomAuthenticationEntryPoint customAuthenticationEntryPoint;//自定义的过滤器@AutowiredJwtFilter jwtFilter;//数据库底层操作@AutowiredSecurityUserServices userServices;//加密器的设置@Beanpublic PasswordEncoder getPasswordEncoder() {return new BCryptPasswordEncoder();}@BeanSecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {//没有用到禁用csrf拦截httpSecurity.csrf().disable()//session策略为不使用session 因为用的redis.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);httpSecurity//不需要认证的请求 也就是不对以下路径做拦截//但还是会经过过滤链 只是如果请求路径为以下中的任何一个则放行.authorizeRequests().antMatchers(//不拦截登录请求"/user/login" ,//不拦截静态资源文件"/static/**",//不拦截项目资源"/pro/**",//不拦截swagger相关的资源文件"/v2/api-docs","/swagger-resources","/swagger-resources/configuration/*","/swagger-resources/configuration/ui",//不拦截swagger界面"/swagger-ui.html").permitAll()//其他所有的都需要认证.anyRequest().authenticated();//数据库的底层操作httpSecurity.userDetailsService(userServices);//将自定义过滤器加到账号密码验证过滤器之前httpSecurity.addFilterBefore(jwtFilter , UsernamePasswordAuthenticationFilter.class);//自定义异常处理一个是认证错误 一个是权限错误httpSecurity.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint).accessDeniedHandler(customAccessDeniedHandler);//添加跨域httpSecurity.cors();return httpSecurity.build();}@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {// 不需要走过滤链的请求 也就是忽略过滤链 这里忽略druid 不然无法查看return (web) -> web.ignoring().antMatchers("/druid/**" );}//配置跨源访问@BeanCorsConfigurationSource corsConfigurationSource() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",new CorsConfiguration().applyPermitDefaultValues());return source;}//认证管理器 将其包装为Bean 用于自定义过滤器JwtFilter中的认证@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}}
web的一些配置
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {//资源映射器@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {//将地址栏输入的/pro映射到/static/proregistry.addResourceHandler("/pro/**").addResourceLocations("classpath:/static/pro/");}//视图控制器@Overridepublic void addViewControllers(ViewControllerRegistry registry) {}//跨域配置@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("*").allowCredentials(true).maxAge(3600).allowedHeaders("*");}
}
下面是整个用户的包结构,各个类的作用后面会具体讲到。
对于Mybatis的相关类,这里不再赘述。但是其中的实现,跟之前的文章略有出入,所以需要更改一下。

在Spring Security框架中,User类是一个特殊的类,是框架默认的用户类。其中只包含了少数的数据项,诸如账号、密码、权限等,如果需要电话、邮件、地址等数据项,则需要自定义用户类。

如果需要自定义用户类,只需要实现UserDetails 接口即可,最好不要用User作为类名,否则很容易导错包。本项目自定义的用户类如下
@Setter
@Getter
@ToString
public class UserBean implements UserDetails {@ApiModelProperty(value = "用户编号")int id;@ApiModelProperty(value = "用户姓名")String name;@ApiModelProperty(value = "用户密码")private String pw;@ApiModelProperty(value = "用户角色")String role;@Overridepublic Collection extends GrantedAuthority> getAuthorities() {//默认权限为空return null;}@Overridepublic String getPassword() {//数据库存储的不是密文 验证时需要使用密文 所以密码加密在//SecurityUserServices里处理return this.pw;}@Overridepublic String getUsername() {return this.name;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
下面是数据库相关的代码及说明
底层的接口及其配置文件信息
public interface UserBeanDao {//根据用户名称获取用户对象UserBean getUserByUserName(String name);//框架的的认证方式 - 这个UserBean loadUserByUsername(String name);
}
用户的业务逻辑处理接口及实现类: UserBeanServices以及UserBeanServicesImpl
public interface UserBeanServices {UserBean getUserByUserName(String name);//登录接口Result login(String userName , String password);
}
@Service
public class UserBeanServicesImpl implements UserBeanServices {//在这里获取Spring Security的认证管理器 用于认证@Autowiredprivate AuthenticationManager authenticationManager;//redis的工具类 用于认证成功后向redis中存入登录信息@AutowiredRedisUtil redisUtil;//数据库底层操作@AutowiredUserBeanDao dao;@Overridepublic UserBean getUserByUserName(String name) {return dao.getUserByUserName(name);}//登录接口 使用Spring Security的登录逻辑@Overridepublic Result login(String userName, String password) {//由Spring Security认证管理器实现认证Authentication authentication = authenticationManager.authenticate(//将用户名和密码 包装成一个UsernamePasswordAuthenticationToken 用于认证UsernamePasswordAuthenticationToken.unauthenticated(userName , password));//将得到认证信息强转为用户实体UserBean userBean = (UserBean) authentication.getPrincipal();//以登陆的用户名获取tokenString token = TokenUtil.getToken(userBean.getName());//以键值对信息 向redis中存入 用户名 - 用户信息redisUtil.setCacheObject("loginId:"+ userBean.getUsername() , userBean);//将token和用户信息返回给前端HashMap map = new HashMap<>();map.put("token" , token);map.put("user" , userBean);System.out.println("登陆成功");return ResultUtil.success(ResultCode.LOGINSUCCESS , map);}
}
在Spring Security框架中对于从数据库中取出用户的信息,也定义了一个接口
UserDetailsService,该类仅有一个方法:loadUserByUsername(String username),用于根据所给用户名查出用户信息。也就是我们之前目录结构的里面的SecurityUserServices
@Service
public class SecurityUserServices implements UserDetailsService {//给Security提供的认证逻辑的底层@AutowiredUserBeanDao userBeanDao;//实现loadUserByUsername方法@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名取出信息UserBean userBean = userBeanDao.loadUserByUsername(username);//默认取出的密码是密文 因为我数据库里没加密 所以这里加密userBean.setPw(new BCryptPasswordEncoder().encode(userBean.getPw()));return userBean;}
}
用户接口的控制器
@Api(tags = {"02 用户管理"} , position = 2)
@RestController
@RequestMapping("/user/")
public class UserController {@AutowiredUserBeanServices userBeanServices;@AutowiredRedisUtil redisUtil;@ApiOperation(value = "根据名称获取用户信息" , notes = "用户名为字符串")@PostMapping("/getUserBeanByName")public Result getUserBeanByName(@RequestParam("name") String name) {UserBean userBean = userBeanServices.getUserByUserName(name);if(userBean != null)return ResultUtil.success(ResultCode.SUCCESS , userBean);elsereturn ResultUtil.success(ResultCode.ERROR);}@ApiOperation(value = "登录" , notes = "登录")@PostMapping("/login")public Result login(@RequestParam("username") String name ,@RequestParam("password") String password) {System.out.println("自定义登录逻辑");return userBeanServices.login(name , password);}@ApiOperation(value = "登出" , notes = "登出")@PostMapping("/lg")public Result lg() {//清除redis里的信息UserBean userBean = (UserBean) SecurityContextHolder.getContext().getAuthentication().getPrincipal();SecurityContextHolder.clearContext();//清除当前的信息if (redisUtil.deleteObject("loginId:" + userBean.getUsername())) {return ResultUtil.success("退出登录成功");}elsereturn ResultUtil.error(ResultCode.ERROR);}
}
在Spring Security中最常见的两中错误就是:
AuthenticationException:认证异常 - 密码错误、身份过期等。在这里由CustomAuthenticationEntryPoint处理
AccessDeniedException:拒绝连接异常 - 常见的是权限不够。在这里由CustomAccessDeniedHandler处理
对于前一种登陆时的错误,我们要返回提示信息,比如:账号或密码错误什么的。对于身份过期,我们要转到登录页,让他重新登陆。
第二种错误可以只返回一个提示信息,提醒他权限不够。
自定义认证异常处理,用于处理登录信息出错,身份过期等情况。
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response,AuthenticationException authException) throws IOException {if(isAjaxRequest(request)){//ajax请求的异常 一般是token过期 //返回状态码410 让前端转到登录页response.sendError(HttpServletResponse.SC_UNAUTHORIZED,authException.getMessage());}else {if(authException instanceof BadCredentialsException|| authException instanceof InternalAuthenticationServiceException) {//这里认证失败的错误处理 密码错误或者账号错误System.out.println("账号或者密码错误");String json = JSON.toJSONString(ResultUtil.error(ResultCode.UORPWRONG));ResponseUtils.writMsgToResponse(response, json);} else {//防止地址栏输入 进入到另外的界面 这里直接重定向到登录页让用户登录response.sendRedirect("/pro/html/login.html");}}}//判断是否是ajax请求public static boolean isAjaxRequest(HttpServletRequest request) {//部分版本可能没有 那么就需要发起请求时 自己设置一下请求头里的属性return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));}
}
自定义拒绝连接异常,用于处理权限错误。
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException accessDeniedException) throws IOException, ServletException {//权限不够时的处理Result result = ResultUtil.error("权限不够,请确认当前账户的身份。");String json = JSON.toJSONString(result);//处理异常ResponseUtils.writMsgToResponse(response, json);}
}
假如要实现这样的一个功能:根据传来的token判断当前用户名的用户名信息是否存在于Redis中,如果存在则表示已登录,则不再需要认证。否则,则表示未登录,需要认证。
那我们应该怎么做呢?
我们知道Spring Security其本质就是一系列的过滤器 -- Filter,我们可以在Spring Security的过滤链 -- FilterChain中加入一个过滤器来实现自定义的功能。那我们该怎么做?只需要思考以下两个问题:
Spring Security框架的FilterChain(过滤链)中FilterChain中的中的什么位置以下内容来自于本人翻译Spring官网里的Spring Security,翻译文章的地址为: SpringSecurity官网的Architecture部分的翻译
Security Filters(安全过滤器)
Security Filter 被SecurityFilterChain的API插入到FilterChainProxy中。这些Filter的顺序是很重要的。通常我们并不需要知道SpringSecurity中Filter的顺序。但有时候,知道顺序是有帮助的。
以下是SpringSecurity Filter的综合清单顺序:
//强制要求创建Session过滤器 用于强制生成Session
//与下面的SessionManagementFilter相关联
ForceEagerSessionCreationFilter//通道过滤器 规定哪些请求必须走http协议 哪些走https协议
ChannelProcessingFilter//Web异步管理集成过滤器
//此过滤器使得WebAsync异步线程能够获取到当前认证信息
WebAsyncManagerIntegrationFilter//安全上下文存在过滤器
//控制SecurityContext的在一次请求中的生命周期,请求结束时清空,防止内存泄漏。
SecurityContextPersistenceFilter//请求头写入过滤器 用来给相应添加一些header
HeaderWriterFilter//跨域过滤器 一般用在跨域请求资源的时候
CorsFilter//跨域请求伪造过滤器 用于防止csrf攻击
CsrfFilter//登出过滤器 退出登录时的逻辑
LogoutFilter//Oauth2请求鉴权重定向过滤器,需配合OAuth2.0的模块使用
OAuth2AuthorizationRequestRedirectFilter//Saml2单点认证过滤器。需配合Spring Security SAML模块使用。
Saml2WebSsoAuthenticationRequestFilter//X.509证书认证过滤器。
X509AuthenticationFilter//预认证处理的抽象过滤器 自定义过滤器的基类
AbstractPreAuthenticatedProcessingFilter//CAS 单点登录认证过滤器 。配合Spring Security CAS模块使用
CasAuthenticationFilter//OAuth2 登录认证过滤器。处理通过 OAuth2 进行认证登录的逻辑
OAuth2LoginAuthenticationFilter//SMAL 的 SSO 单点登录认证过滤器
Saml2WebSsoAuthenticationFilter//用户名密码认证过滤器。 最主要的认证过滤器 账户的验证在这里进行
UsernamePasswordAuthenticationFilter//OpenID认证过滤器。需要在依赖中依赖额外的相关模块才能启用它
OpenIDAuthenticationFilter//默认登入页生成过滤器。默认 /login
DefaultLoginPageGeneratingFilter//默认登出页生成过滤器。 默认 /logout
DefaultLogoutPageGeneratingFilter//session管理,用于判断session是否过期。该过滤器可能会被多次执行
ConcurrentSessionFilter//摘要认证过滤器。Web 应用程序中流行的可选的身份验证机制
DigestAuthenticationFilter//Bearer标准token认证过滤器。
BearerTokenAuthenticationFilter//标准认证过滤器 Web 应用程序中流行的可选的身份验证机制
//负责处理 HTTP 头中显示的基本身份验证凭据
BasicAuthenticationFilter//请求缓存过滤器。主要作用是认证完成后恢复认证前的请求继续执行
RequestCacheAwareFilter//安全上下文持有者感知请求过滤器 用于实现servlet的一些api
SecurityContextHolderAwareRequestFilter//Jaas认证过滤器。适用于JAAS (Java 认证授权服务)
JaasApiIntegrationFilter//记住我认证过滤器。处理“记住我”功能的过滤器。
RememberMeAuthenticationFilter//匿名认证过滤器 如果访问不需要授权的资源 则以匿名身份访问
AnonymousAuthenticationFilter//OAuth2授权码过滤器
OAuth2AuthorizationCodeGrantFilter//Session管理器过滤
//其中SessionAuthenticationStrategy 用于管理 Session
SessionManagementFilter//异常翻译过滤器 过滤链中的异常会等到了此处再一并处理
ExceptionTranslationFilter//过滤器安全拦截器 这个过滤器决定了访问特定路径应该具备的权限
FilterSecurityInterceptor//账户切换过滤器 用来做账户切换的
SwitchUserFilter
(上述信息参考:Spring Security详解一:过滤器)
其实简单的配置,只需要注意一个
过滤器 – UsernamePasswordAuthenticationFilter,用户名密码认证过滤器。用于验证用户的用户名和密码是否正确。这里后面会再次提到。
要想被Spring Security识别,该过滤器可以继承OncePerRequestFilter
严谨的说只要继承了 Filter即可,但是在Spring体系中,推荐使用OncePerRequestFilter来实现,它可以确保一次请求只会通过一次该过滤器(Filter实际上并不能保证)
先看JwtFilter的作用:判断当前用户名的用户名信息是否存在于Redis中,如果存在则表示已登录,则不再需要认证。否则,则表示未登录,需要认证。
那么这个过滤器根据其功能自然是要在具体认证发生之前的,还记得刚刚说的过滤链中用于具体认证的过滤器吗?没错,是它 – UsernamePasswordAuthenticationFilter,那么该自定义过滤器放到UsernamePasswordAuthenticationFilter之前即可。
也就对应了Spring Security配置文件中的
httpSecurity.addFilterBefore(jwtFilter ,UsernamePasswordAuthenticationFilter.class);
自定义的token认证过滤器
@Component
public class JwtFilter extends OncePerRequestFilter {//redis工具类@Autowiredprivate RedisUtil redisUtil;//具体功能在此方法内写@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,FilterChain filterChain)throws ServletException, IOException {//登录请求或者token为空则表示未登录 需要验证String path = httpServletRequest.getServletPath();System.out.println("请求地址:" + path);String token = httpServletRequest.getHeader("token");if (token == null|| "".equals(token) ||"/user/login".equals(path)) {//无token或者登录 走UsernamePasswordAuthenticationFilterfilterChain.doFilter(httpServletRequest, httpServletResponse);return;}//解析tokenString userid = null;try {//获取token中的用户名信息userid = TokenUtil.getMsgFromToken(token);} catch (ExpiredJwtException e) {httpServletRequest.setAttribute("data","身份已过期,请重新登录。");//直接重定向到错误信息界面httpServletRequest.getRequestDispatcher("pro/html/error.html").forward(httpServletRequest,httpServletResponse);return;}catch (UnsupportedJwtException | MalformedJwtException | SignatureException e){//否则的话 交给后面的过滤器处理throw new RuntimeException("非法token");}//组成键 前缀可以自定义 最好具有一定地辨识度String redisKey = "loginId:" + userid;//从redis中获取用户信息UserBean user = redisUtil.getCacheObject(redisKey);if (Objects.isNull(user)) {throw new RuntimeException("用户未登录");}//获取权限信息封装到Authentication中 表明已认证SecurityContextHolder.getContext().setAuthentication(UsernamePasswordAuthenticationToken.authenticated(user,null,user.getAuthorities()));//放行filterChain.doFilter(httpServletRequest, httpServletResponse);}
}
Redis工具类 用于简单的向Redis中存取数据
@Component
public class RedisUtil {@Autowiredprivate RedisTemplate redisTemplate;//设置token信息到redis中public void setCacheObject(final String key, final T value) {redisTemplate.opsForValue().set(key, value);}//根据用户名从redis获取token信息public T getCacheObject(final String key) {ValueOperations operation = redisTemplate.opsForValue();return operation.get(key);}//删除单个对象public boolean deleteObject(final String key) {return redisTemplate.delete(key);}
}
用于生成和解析token,直接去下面找工具类即可
Java生成token的工具类(对称签名)
用于向Response中写入数据,主要是字符串。
public class ResponseUtils {/*** @description 向响应中写入信息* @author 三文鱼先生* @date 11:47 2022/12/2* @param response* @param string* @return java.lang.String**/public static void writMsgToResponse(HttpServletResponse response, String string) {try {response.setStatus(200);response.setContentType("application/json");//字符编码response.setCharacterEncoding("utf-8");response.getWriter().print(string);}catch (IOException e) {e.printStackTrace();}}}

尝试调用登录接口 如果看到以下信息内容 则表明成功引入Spring Security框架进行了验证。

既然项目搭好了 我们现在来搭建一下系统的界面,从前端走一下认证流程。
JQuery不会引入的话,参考以下文章:简单引入JQuery
前端的目录结构如下

错误信息页
错误信息页
首页
登陆成功的首页
js示例文件
这里就不放js代码了,有需要的,自行去官网找资源即可。也可以参考以下文章: 简单引入JQuery
最终运行的配置文件
server:#配置端口及编码信息port: 9800tomcat:uri-encoding: UTF-8mybatis:#配置mapper的指定路径mapper-locations: classpath*:com/demo/**/dao/*.xml#防止空值异常报错configuration:jdbc-type-for-null: 'null'spring:#设置高版本Swagger匹配策略mvc:pathmatch:matching-strategy: ant_path_matcher#配置数据源datasource:name: mydatasource#自定义数据源type: com.alibaba.druid.pool.DruidDataSourcedruid:#监控统计拦截的filtersfilters: statdriver-class-name: com.mysql.cj.jdbc.Driver#连接基本属性url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=UTCusername: rootpassword: root#配置初始化大小/最小/最大initial-size: 1min-idle: 1max-active: 20#获取连接等待超时时间max-wait: 60000#间隔多久进行一次检测,检测需要关闭的空闲连接time-between-eviction-runs-millis: 60000#一个连接在池中最小生存的时间min-evictable-idle-time-millis: 300000#用来验证连接是否有效的sqlvalidation-query: SELECT 'x'#申请连接时检测空闲时间test-while-idle: true#从连接池获取连接时是否检查连接有效性,true检查,false不检查test-on-borrow: false#归还连接时是否检查连接有效性,true检查,false不检查test-on-return: false#打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为falsepool-prepared-statements: false#连接池中最大的预处理连接数量max-pool-prepared-statement-per-connection-size: 20stat-view-servlet:enabled: falseredis:host: localhostport: 6379logging:level:# 给指定的包设置日志级别com.demo.user: DEBUG#Swagger2是否可用
swagger2:enable: true
用于快速建user表
SET FOREIGN_KEY_CHECKS=0;-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`id` int(20) NOT NULL,`name` varchar(20) DEFAULT NULL,`pw` varchar(20) DEFAULT NULL,`role` varchar(20) DEFAULT NULL COMMENT '用户角色',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1001', 'zs', '12345', 'user');
INSERT INTO `user` VALUES ('1002', 'ls', '1234', 'admin');
INSERT INTO `user` VALUES ('1004', 'ww', 'qwer', null);
INSERT INTO `user` VALUES ('1005', 'zl', '1234457', null);
INSERT INTO `user` VALUES ('1008', '赵六', 'qwer', null);
INSERT INTO `user` VALUES ('1009', '李四', '12345', null);
INSERT INTO `user` VALUES ('1010', '123', '123', null);
INSERT INTO `user` VALUES ('1011', '1', '1', null);

登录成功会转到用户首页

这里以点击一个按钮为演示

表明带token的请求不再被拦截,可以正常执行。
点击之后会转到登录页。即使点击浏览器的后退按钮,再操作界面,也需要再次登录。
同时会清除前端本地存储的用户信息,以及redis上的用户信息。

当输入错误的账号或者密码时:

浏览器地址栏直接输入用户首页,回车访问

会转到登录页 让用户登录
不过这里 要保证token为空或者是错误的

修改本地存储的token,点击按钮

会直接转到登录页

过期token发起的的请求返回

在弄这个的时候遇到的问题还是蛮多的。
在这里卡了一会,后面想到直接由前端通过token判断拦截,后端不对前端界面做拦截。
认证失败本来是想由指定的认证异常处理的

但是没有生效,最后写到了CustomAuthenticationEntryPoint里面。
有时候你的项目里有资源,但是你却访问不到。大概率是资源导出配置的问题。
直接去target目录下找找有没有,没有的话直接复制进去。

写这一篇大概前前后后写了快两万五千字,中间很多次去Spring官网查相关英文的文档,csdn上的大部分是低版本的Spring Security配置,用高版本的还要另外找资料,这些花了很多的时间。
还有就是前端界面,本来想用Thymeleaf的,后面又觉得又不符合前后端分离的架构,再加上实在不熟,就又自己去找了资料学了下前端的东西,写了点前端的界面。
删删减减,涂涂改改终于是结束了。今晚看球!