/**
通过自定义UsernamePasswordAuthenticationFilter和BasicAuthenticationFilter 实现
(验证码验证可以在UsernamePasswordAuthenticationFilter之前再加入一个Filter过滤器来处理)
-
/
/* - 自定义JWT登录过滤器
- 验证用户名密码正确后,生成一个token,并将token返回给客户端
- 该类继承自UsernamePasswordAuthenticationFilter,重写了其中的3个方法
- attemptAuthentication :接收并解析用户凭证。
- successfulAuthentication :用户成功登录后,这个方法会被调用,我们在这个方法里生成token。
- unsuccessfulAuthentication : 认证失败,异常往外抛,让全局异常捕捉(当然你也可以判断异常类型,返回不同的code)
- @author cola
- AuthenticationManager 是什么
- 1.https://blog.csdn.net/weixin_45802793/article/details/121891982
*/
/** 概念解释
1。Spring Security,它是用于解决认证与授权的框架。Spring Security有默认的登录账号和密码,用户名user,密码是随机的,
每次启动项目都会重新生成一个。
它要求所有的请求都必须先登录才允许访问。
2。
UsernamePasswordAuthenticationFilter 用户名密码过滤器
package org.springframework.security.web.authentication;
UsernamePasswordAuthenticationFilter是AbstractAuthenticationProcessingFilter
针对使用用户名和密码进行身份认证而定制化的一个过滤器。其添加是在调用http.formLogin()时作用,
默认的登录请求pattern为"/login",并且为POST请求。当我们登录的时候,也就是匹配到loginProcessingUrl,
这个过滤器就会委托认证管理器authenticationManager来验证登录
https://blog.csdn.net/qq_41071876/article/details/122067412
AuthenticationManager
package org.springframework.security.authentication;
AuthenticationMananger作为整个身份验证核心最外层的封装负责与外部使用者进行交互。
AuthenticationMananger接口有且仅有一个对外的服务便是“身份验证”。
https://www.cnblogs.com/loveLands/articles/16535805.html
- */
/**
Spring Security的标准认证处理链:
1。UsernamePasswordAuthenticationFilter:该过滤器用于获取用户输入的用户名和密码,
并将其封装在UsernamePasswordAuthenticationToken对象中。它还检查用户输入的凭据是否正确,
并将其发送到AuthenticationManager进行身份验证。
2。BasicAuthenticationFilter:该过滤器用于处理基本认证,即通过HTTP消息头传递的用户名和密码。
在进行基本认证时,前端应用程序会将用户凭据(base64编码的)添加到HTTP请求标头中。
BasicAuthenticationFilter在此通过解码这些凭据并将其发送到AuthenticationManager进行身份验证。
3。SecurityContextHolderAwareRequestFilter:该过滤器用于包装HttpServletRequest对象,
以便将其传递到Spring Security的AuthenticationAwareWebInvocationPrivilegeEvaluator。
这主要是用于在页面或JavaScript中检查是否已经经过身份验证。
4。AnonymousAuthenticationFilter:该过滤器负责提供匿名身份验证机制。
如果用户未经过身份验证,则该过滤器会向SecurityContext注入一个AnonymousAuthenticationToken对象。
5。SessionManagementFilter:该过滤器管理用户的会话,包括创建新的会话、检查会话是否过期以及将会话绑定到用户的身份验证,
以防止会话劫持攻击。
6。ExceptionTranslationFilter:该过滤器处理由其他过滤器抛出的异常。它查找最合适的异常处理程序,
例如包含特定响应头和状态代码的RESTful API异常处理程序或进行页面重定向的MVC样式的异常处理程序。
7。FilterSecurityInterceptor:该过滤器在处理请求之前检查当前用户是否具有所请求的资源的访问权限。
如果用户没有权限,则FilterSecurityInterceptor将阻止请求并返回HTTP 403 Forbidden响应。
https://blog.csdn.net/qq_44182694/article/details/130903820
- */
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* 尝试身份认证(接收并解析用户凭证)
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
try {
SysOpr user = new ObjectMapper().readValue(req.getInputStream(), SysOpr.class);
/**
4。https://blog.csdn.net/UUaple_/article/details/110471473
LoginModeConstant
因为没有 验证码 这部分被省略了
//检查登陆方式
if(!ClassUtils.equalStringPropertyValue(LoginModeConstant.class,loginForm.getLoginMode())){
logger.error("未选择登录方式");
throw new AuthenticationServiceException("服务异常");
}
//检查验证码 也缺少LoginModeConstant
checkVerificationCode(loginForm);
* */
Authentication authentication= authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),
user.getPassword(),
new ArrayList<>())
);
return authentication;
} catch (IOException e) {
throw new RuntimeException(e);
/**
UsernameNotFoundException 用户找不到
https://blog.csdn.net/sayWhat_sayHello/article/details/89709289
* */
} catch (UsernameNotFoundException e){
//拦截 重写 返回方法 UsernameNotFoundException
setMessage(e,res);
return null;
/**
https://blog.csdn.net/m0_48984294/article/details/125239397
https://blog.csdn.net/weixin_40816738/article/details/126820191
* */
} catch (BadCredentialsException e){
//拦截 重写 返回方法 UsernameNotFoundException
setMessage(e,res);
return null;
}
}
/** 检查验证码
protected void checkVerificationCode(SysLoginForm loginForm) throws GalaxyException{
RedisUtils redisUtils = (RedisUtils) SpringContextUtils.getBean("redisUtils");
String key = null;
String value = null;
if(LoginModeConstant.PHONE_VERIFICATION_CODE.equals(loginForm.getLoginMode())){
key = RedisKeys.PHONE_VERIFICATION_LOGIN_PREFIX + loginForm.getPhone();
value = redisUtils.get(key);
if(StrUtil.isBlank(value) || !value.equals(loginForm.getPhoneVerificationCode())){
throw new AuthenticationServiceException("验证码错误");
}
}else if(LoginModeConstant.EMAIL_VERIFICATION_CODE.equals(loginForm.getLoginMode())){
key = RedisKeys.EMAIL_VERIFICATION_LOGIN_PREFIX + loginForm.getEmail();
value = redisUtils.get(key);
if(StrUtil.isBlank(value) || !value.equals(loginForm.getEmailVerificationCode())){
throw new AuthenticationServiceException("验证码错误");
}
}else {
key = RedisKeys.IMAGE_VERIFICATION_LOGIN_PREFIX + loginForm.getUuid();
value = redisUtils.get(RedisKeys.IMAGE_VERIFICATION_LOGIN_PREFIX + loginForm.getUuid());
if(StrUtil.isBlank(value) || !value.equals(loginForm.getImageVerificationCode())){
throw new AuthenticationServiceException("验证码错误");
}
}
redisUtils.delete(key);
}
* */
/**
和 认证失败 的处理方式 一样
* */
private void setMessage(AuthenticationException e, HttpServletResponse res){
R result = R.failed(e.getMessage());
String message = JSONUtil.toJsonStr(result);
res.setContentType("application/json;charset=UTF-8");
try {
res.getWriter().write(message);
} catch (IOException ioException) {
throw new RuntimeException(e);
}
}
/**
* 认证成功(用户成功登录后,这个方法会被调用,我们在这个方法里生成token)
* 返回客户端
*/
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
// builder the token
String token = null;
try {
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
// 定义存放角色集合的对象
List roleList = new ArrayList<>();
for (GrantedAuthority grantedAuthority : authorities) {
roleList.add(grantedAuthority.getAuthority());
}
// 生成token start
Calendar calendar = Calendar.getInstance();
// 设置签发时间
Date now = calendar.getTime();
/**
// JwtConfig jwtConfig = (JwtConfig) SpringContextUtils.getBean("jwtConfig");
// Date time = new Date(now.getTime() + jwtConfig.getExpiration());
* */
// 设置过期时间 24小时
calendar.setTime(new Date());
calendar.add(Calendar.HOUR, 24);
Date time = calendar.getTime();
token = Jwts.builder()
.setSubject(auth.getName() + "-" + roleList)
//签发时间
.setIssuedAt(now)
//过期时间
.setExpiration(time)
//采用什么算法是可以自己选择的,不一定非要采用HS512
.signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY)
.compact();
// 生成token end
/**
// 登录成功后,返回token
TokenInfoDTO dto = new TokenInfoDTO();
dto.setToken(token);
dto.setUserName(auth.getName());
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSON.toJSONString(DataResultUtil.success(dto)));
logger.debug("认证成功");
logger.debug("用户:" + auth.getName());
ConstantKey 存放常量的地方 估计是胡峰自己写的
* */
// 登录成功后,返回token到body里面
Map<String, Object> resultMap = new HashMap<>();
resultMap.put(ConstantKey.HEADER_KEY, ConstantKey.BEARER + token);
R result = R.ok(resultMap);
response.getWriter().write(JSON.toJSONString(result));
} catch (Exception e) {
/**
logger.error("认证异常",e);
response.getWriter().write(JSON.toJSONString(DataResultUtil.error("网络异常")));
* */
e.printStackTrace();
}
}
/**
Security 登录异常不能全局捕捉,通过 unsuccessfulAuthentication 捕捉并返回,
其实在这个地方再往外抛异常好像是可以,一开始我是这样做的,后来也失效了。就改成这样了,功力不够啊,求大神解惑。
* */
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
/**
logger.debug("认证失败");
logger.debug(authenticationException.getMessage());
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSON.toJSONString(DataResultUtil.error(authenticationException.getMessage())));
* */
}
}