1 加入pom.xml
<!--Mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2 配置application.properties
#serverPort
server.port=9001
#datasource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://xxx:3306/springboot-demo07-security?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.druid.initialSize=5
spring.datasource.druid.minIdle=5
spring.datasource.druid.maxActive=20
spring.datasource.druid.maxWait=60000
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.validationQuery=SELECT 1
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=true
spring.datasource.druid.testOnReturn=false
spring.datasource.druid.poolPreparedStatements=true
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.filters=stat,wall
spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1
#redis
spring.redis.host=xxx
spring.redis.port=6379
spring.redis.password=xxx
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=500
spring.redis.jedis.pool.min-idle=0
spring.redis.lettuce.shutdown-timeout=0
#thymeleaf
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
#mybatis-plus
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.auto-mapping-behavior=full
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath*:mapper/*Mapper.xml
#pageHelper
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
pagehelper.pageSizeZero=true
pagehelper.params=count=countSql
#jwt
jwt.header=Authorization
jwt.expire=604800
jwt.secret=ji8n3439n439n43ld9ne9343fdfer49h
3 security结构
4 定义UserDetails(交给security管理的用户)
package com.grm.security.details;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* 登录用户身份权限
*
* @author gaorimao
* @date 2022/02/07
*/
public class LoginUser implements UserDetails {
/**
* 用户id
*/
private Long userId;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 已授予的权限
*/
private Collection<? extends GrantedAuthority> authorities;
public LoginUser(Long userId, String username, String password, Collection<? extends GrantedAuthority> authorities) {
this.userId = userId;
this.username = username;
this.password = password;
this.authorities = authorities;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
5 定义UserDetailsService实现
security自动进行数据库校验,只校验用户名,密码校验security自己封装实现了,如果要自定义密码校验,需要实现DaoAuthenticationProvider
package com.grm.security.details;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.grm.common.Result;
import com.grm.entity.SysUser;
import com.grm.service.SysUserService;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 用户验证处理
*
* @author gaorimao
* @date 2022/02/07
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private SysUserService sysUserService;
@Autowired
private HttpServletResponse response;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查用户
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
SysUser user = sysUserService.getOne(wrapper);
if (ObjectUtils.isEmpty(user)) {
Result.renderJsonStr(response,Result.failed(500,"登录用户:" + username + "不存在"));
}
return createLoginUser(user);
}
/**
* 创建登录用户
*
* @param sysUser 系统用户
* @return {@link UserDetails}
*/
private UserDetails createLoginUser(SysUser sysUser) {
List<GrantedAuthority> grantedAuthorities = getUserAuthority(sysUser.getId());
return new LoginUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), grantedAuthorities);
}
/**
* 获取用户权限信息(角色、菜单权限)
*
* @param userId 用户id
* @return {@link List}<{@link GrantedAuthority}>
*/
private List<GrantedAuthority> getUserAuthority(Long userId) {
// ROLE_admin,ROLE_normal,sys:user:list,....
String authority = sysUserService.getUserAuthorityInfo(userId);
//为用户分配权限,上面的配置类会根据权限来限制访问,产生不同结果。
return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
}
}
其中,查用户权限的代码
package com.grm.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.grm.entity.SysMenu;
import com.grm.entity.SysRole;
import com.grm.entity.SysUser;
import com.grm.mapper.SysMenuMapper;
import com.grm.mapper.SysRoleMapper;
import com.grm.mapper.SysUserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* 系统用户服务impl
*
* @author gaorimao
* @date 2022/02/07
*/
@Slf4j
@Service
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysRoleMapper sysRoleMapper;
@Autowired
private SysMenuMapper sysMenuMapper;
/**
* 获取用户权限信息(ROLE_admin,ROLE_normal,sys:user:list,sys:role:list)
*
* @param userId 用户id
* @return {@link String}
*/
public String getUserAuthorityInfo(Long userId) {
String authority;
// 1.获取角色代码
QueryWrapper<SysRole> sysRoleQueryWrapper = new QueryWrapper<>();
String queryRoleIdsSql = "select role_id from sys_user_role where user_id = " + userId;
sysRoleQueryWrapper.inSql("id", queryRoleIdsSql);
List<SysRole> roles = sysRoleMapper.selectList(sysRoleQueryWrapper);
String roleCodes = "";
if (roles.size() > 0) {
// 查询角色代码
roleCodes = roles.stream().map(r -> "ROLE_" + r.getCode()).collect(Collectors.joining(","));
}
authority = roleCodes.concat(",");
// 2.获取权限代码
// 2.1 如果角色有admin,默认所有菜单权限
String menuPerms = "";
if (roles.size() > 0) {
List<SysRole> adminRoles = roles.stream().filter(role -> role.getCode().equals("admin")).collect(Collectors.toList());
if (adminRoles.size() > 0) {
List<SysMenu> menus = sysMenuMapper.selectList(null);
// 查询菜单权限
menuPerms = menus.stream().map(m -> m.getPerms()).collect(Collectors.joining(","));
}else{
menuPerms = getPermsAuthorityByUserId(userId);
}
}
authority = authority.concat(menuPerms);
return authority;
}
/**
* 通过userId查询所有相关的menuIds
*
* @param userId 用户id
* @return {@link String}
*/
private String getPermsAuthorityByUserId(Long userId) {
List<Long> menuIds = sysUserMapper.queryMenuIdsByUserId(userId);
String menuPerms = "";
if (menuIds.size() > 0) {
List<SysMenu> menus = sysMenuMapper.selectBatchIds(menuIds);
// 查询菜单权限
menuPerms = menus.stream().map(m -> m.getPerms()).collect(Collectors.joining(","));
}
return menuPerms;
}
}
如果要进行自定义密码验证,代码如下(一般如果有验证码功能的话需要加上这个步骤)
package com.grm.security.login;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 自定义密码比对逻辑,需要自定义盐值时可使用此类
*
* @author gaorimao
* @date 2022/02/08
*/
@Slf4j
public class PasswordCheckHandler extends DaoAuthenticationProvider {
public PasswordCheckHandler(UserDetailsService userDetailsService) {
super();
// 这个地方一定要对userDetailsService赋值,不然userDetailsService是null
setUserDetailsService(userDetailsService);
}
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// authentication.getCredentials()得到的是密码
if (authentication.getCredentials() != null) {
String password = authentication.getCredentials().toString();
String encodePassword = new BCryptPasswordEncoder().encode(password);
String dbPassword = userDetails.getPassword();
log.info("[PasswordCheckHandler] password = {},encodePassword = {} ,dbPassword = {}", password, encodePassword, dbPassword);
if (!encodePassword.equals(dbPassword)) {
throw new BadCredentialsException("密码错误!");
}
}
}
}
6 实现UsernamePasswordAuthenticationFilter,获取json请求的登录参数
package com.grm.security.login;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* 登录参数为json格式时,security无法获取,需要重写下
* <p>
* 单体不分离项目不需要,直接form表单提交获取登录参数即可
*
* @author gaorimao
* @date 2022/02/08
*/
@Slf4j
public class LoginParamFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getMethod().equalsIgnoreCase("POST")) {
ObjectMapper mapper = new ObjectMapper();
InputStream inputStream;
Map<String, String> authenticationBean;
try {
inputStream = request.getInputStream();
authenticationBean = mapper.readValue(inputStream, Map.class);
String username = authenticationBean.get("username");
String password = authenticationBean.get("password");
log.info("[LoginParam] username = {},password = {}", username, password);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} catch (IOException e) {
throw new AuthenticationServiceException("Authentication method io exception: " + request.getMethod());
}
}
// 只能是post请求
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
}
7 登陆成功、登录失败、退出登录成功处理
登录成功(token返回给前端,token存储在redis中)
package com.grm.security.login;
import com.alibaba.fastjson.JSON;
import com.grm.common.Result;
import com.grm.security.details.LoginUser;
import com.grm.util.JwtUtil;
import com.grm.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 登录成功处理程序
*
* @author gaorimao
* @date 2022-02-04
*/
@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private RedisUtil redisUtil;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.info("[Login] success,loginUser={}", JSON.toJSONString(loginUser));
response.setContentType("application/json;charset=utf-8");
String token = jwtUtil.createToken(loginUser.getUsername());
// 把token存入redis缓存
redisUtil.set(""+loginUser.getUsername(),token,60*60*24*7);
Result result = Result.success("登录成功!",token);
String jsonResult = JSON.toJSONString(result);
PrintWriter out = response.getWriter();
out.write(jsonResult);
out.flush();
out.close();
}
}
登陆失败
package com.grm.security.login;
import com.alibaba.fastjson.JSON;
import com.grm.common.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 登录失败处理程序
*
* @author gaorimao
* @date 2022-02-04
*/
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
response.setContentType("application/json;charset=utf-8");
Result result = Result.failed(500,"请检查用户名或密码是否正确!");
String jsonResult = JSON.toJSONString(result);
PrintWriter out = response.getWriter();
out.write(jsonResult);
out.flush();
out.close();
}
}
退出登录
package com.grm.security.logout;
import com.grm.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 退出登录成功
*
* @author gaorimao
* @date 2022-02-08
*/
@Slf4j
@Component
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
@Autowired
private JwtUtil jwtUtil;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("{\"status\":\"ok\",\"msg\":\"登出成功\"}");
out.flush();
out.close();
}
}
8 鉴权失败(匿名用户和已认证的用户)
匿名用户鉴权失败
package com.grm.security.perms;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* AuthenticationEntryPoint用来解决匿名用户访问无权限资源时的异常
*
* @author gaorimao
* @date 2022/02/08
*/
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendError(HttpStatus.UNAUTHORIZED.value(),exception.getMessage());
}
}
认证过的用户鉴权失败
package com.grm.security.perms;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* AccessDeniedHandler用来解决认证过的用户访问无权限资源时的异常
*
* @author gaorimao
* @date 2022/02/08
*/
@Slf4j
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException, ServletException {
response.sendError(HttpStatus.UNAUTHORIZED.value(), exception.getMessage());
}
}
9 security核心配置类(SecurityConfig)
package com.grm.security;
import com.grm.security.details.UserDetailsServiceImpl;
import com.grm.security.login.LoginFailureHandler;
import com.grm.security.login.LoginParamFilter;
import com.grm.security.login.LoginSuccessHandler;
import com.grm.security.logout.LogoutSuccessHandlerImpl;
import com.grm.security.perms.MyAccessDeniedHandler;
import com.grm.security.perms.MyAuthenticationEntryPoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* security配置认证和授权
*
* @author gaorimao
* @since 2022-02-03
*/
@Slf4j
@Configuration
//开启Spring Security的功能
@EnableWebSecurity
//prePostEnabled属性决定Spring Security在接口前注解是否可用@PreAuthorize,@PostAuthorize等注解,设置为true,会拦截加了这些注解的接口
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 放行的资源列表
*/
private static final String[] PASS_URL = {"/", "/login", "/index", "/logout", "/css/**", "/image/**", "/js/**", "/LightYear/**"};
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private LoginFailureHandler loginFailureHandler;
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定义异常返回信息
http.exceptionHandling()
.authenticationEntryPoint(myAuthenticationEntryPoint)
.accessDeniedHandler(myAccessDeniedHandler);
// CSRF禁用,因为不使用session
http.csrf().disable();
// 开启跨域
http.cors();
// 基于token,所以不需要session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 配置放行的资源和需要鉴权的资源
http.authorizeRequests()
.antMatchers(PASS_URL).permitAll()
.anyRequest().authenticated();
// 登录
http.formLogin().loginPage("/login").loginProcessingUrl("/login");
// 退出登录
http.logout().logoutSuccessHandler(logoutSuccessHandler).logoutUrl("/logout");
// 登陆参数json格式获取,json格式提交时可配置此类
http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
}
/**
* 解决 无法直接注入 AuthenticationManager
*
* @return AuthenticationManager
* @throws Exception Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public UserDetailsService userDetailsService() {
//获取用户账号密码及权限信息
return new UserDetailsServiceImpl();
}
/**
* 配置认证方式等
*
* @param auth 身份验证
* @throws Exception 异常
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 自定义密码校验,密码加盐时可使用此配置
//auth.authenticationProvider(new PasswordCheckHandler(userDetailsService()));
//配置认证方式
auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());
}
/**
* 加密方式
*
* @return {@link BCryptPasswordEncoder}
*/
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
LoginParamFilter loginFilter() throws Exception {
LoginParamFilter loginFilter = new LoginParamFilter();
loginFilter.setAuthenticationManager(authenticationManagerBean());
// 登录成功
loginFilter.setAuthenticationSuccessHandler(loginSuccessHandler);
// 登录失败
loginFilter.setAuthenticationFailureHandler(loginFailureHandler);
return loginFilter;
}
}
10 前端vue项目登录验证
(1)用户名错误时
json响应
页面响应
(2)用户名正确,密码错误时
json
页面响应
(3)用户名,密码都正确
json
页面响应