有时候,我们用到Spring Security验证框架的时候需要用到多个参数,因为默认提供了2个参数username和password,加入我们需要curLoginType这个参数怎么设置
1.重写UsernamePasswordAuthenticationToken这个类,由于这个类已经有spring封装,我们把源代码改一下,重新命名为MyAuthenticationToken,代码如下:
package com.zhengqing.config.security.filter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
import java.util.Map;
public class MyAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 420L;
private final Object principal;
private Object credentials;
/**
* 参数map
*/
private Map<String,Object> map;
public MyAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public MyAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, Map<String,Object> map){
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
this.map=map;
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public Map<String,Object> getParam(){
return map;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
} else {
super.setAuthenticated(false);
}
}
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
注意:我改了代码加了一个属性Map,成为参数Map
/**
* 参数map
*/
private Map<String,Object> map;
2.将自定义用户名密码过滤器中的 UsernamePasswordAuthenticationToken替换成自己的MyAuthenticationToken 即可:
package com.zhengqing.config.security.filter;
import com.alibaba.fastjson.JSONObject;
import com.zhengqing.config.Constants;
import com.zhengqing.config.security.login.CusAuthenticationManager;
import com.zhengqing.utils.MultiReadHttpServletRequest;
import com.zhengqing.config.security.login.AdminAuthenticationFailureHandler;
import com.zhengqing.config.security.login.AdminAuthenticationSuccessHandler;
import com.zhengqing.modules.system.entity.User;
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.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* <p> 自定义用户密码校验过滤器 </p>
*
* @author : zhengqing
* @description :
* @date : 2019/10/12 15:32
*/
@Slf4j
@Component
public class AdminAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
/**
* @param authenticationManager: 认证管理器
* @param adminAuthenticationSuccessHandler: 认证成功处理
* @param adminAuthenticationFailureHandler: 认证失败处理
*/
public AdminAuthenticationProcessingFilter(CusAuthenticationManager authenticationManager, AdminAuthenticationSuccessHandler adminAuthenticationSuccessHandler, AdminAuthenticationFailureHandler adminAuthenticationFailureHandler) {
super(new AntPathRequestMatcher("/login", "POST"));
this.setAuthenticationManager(authenticationManager);
this.setAuthenticationSuccessHandler(adminAuthenticationSuccessHandler);
this.setAuthenticationFailureHandler(adminAuthenticationFailureHandler);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType() == null || !request.getContentType().contains(Constants.REQUEST_HEADERS_CONTENT_TYPE)) {
throw new AuthenticationServiceException("请求头类型不支持: " + request.getContentType());
}
MyAuthenticationToken authRequest;
try {
MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(request);
// 将前端传递的数据转换成jsonBean数据格式
User user = JSONObject.parseObject(wrappedRequest.getBodyJsonStrByJson(wrappedRequest), User.class);
String curLoginType=user.getCurLoginType();
Map<String,Object> map=new HashMap<>();
map.put("curLoginType",curLoginType);
authRequest = new MyAuthenticationToken(user.getUsername(), user.getPassword(), null,map);
authRequest.setDetails(authenticationDetailsSource.buildDetails(wrappedRequest));
} catch (Exception e) {
throw new AuthenticationServiceException(e.getMessage());
}
Authentication ac =this.getAuthenticationManager().authenticate(authRequest);
return ac;
}
}
注意:
// 将前端传递的数据转换成jsonBean数据格式
User user = JSONObject.parseObject(wrappedRequest.getBodyJsonStrByJson(wrappedRequest), User.class);
这个user对象是用户自定义的,我这里加了一个参数为curLoginType,前端curLoginType会自动封装到这个user里面,
后面这段代码
String curLoginType=user.getCurLoginType();
Map<String,Object> map=new HashMap<>();
map.put("curLoginType",curLoginType);
authRequest = new MyAuthenticationToken(user.getUsername(), user.getPassword(), null,map);
就将参数封装到map中去了,这个map就在我的MyAuthenticationToken 这个类的参数map中
3.取值
package com.zhengqing.config.security.login;
import com.alibaba.fastjson.JSONObject;
import com.zhengqing.config.Constants;
import com.zhengqing.config.MyProperties;
import com.zhengqing.config.security.dto.SecurityUser;
import com.zhengqing.config.security.filter.MyAuthenticationToken;
import com.zhengqing.config.security.service.impl.UserDetailsServiceImpl;
import com.zhengqing.modules.system.entity.User;
import com.zhengqing.modules.system.mapper.UserMapper;
import com.zhengqing.utils.ApplicationContextUtil;
import com.zhengqing.utils.MultiReadHttpServletRequest;
import com.zhengqing.utils.PasswordUtils;
import com.zhengqing.utils.RedisUtils;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* <p> 自定义认证处理 </p>
*
* @description :
* @author : zhengqing
* @date : 2019/10/12 14:49
*/
@Component
@Slf4j
public class AdminAuthenticationProvider implements AuthenticationProvider {
@Autowired
UserDetailsServiceImpl userDetailsService;
@Autowired
private UserMapper userMapper;
@Autowired
MyProperties MyProperties;
@Autowired
RedisUtils redisUtils;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MyAuthenticationToken authRequest=(MyAuthenticationToken)authentication;
String curLoginType= MapUtils.getString(authRequest.getParam(),"curLoginType","-1");
// 获取前端表单中输入后返回的用户名、密码
String userName = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
SecurityUser userInfo = (SecurityUser) userDetailsService.loadUserByUsername(userName);
boolean isValid = PasswordUtils.isValidPassword(password, userInfo.getPassword(), userInfo.getCurrentUserInfo().getSalt());
// 验证密码
if (!isValid) {
throw new BadCredentialsException("密码错误!");
}
// 前后端分离情况下 处理逻辑...
// 更新登录令牌
String token = PasswordUtils.encodePassword(String.valueOf(System.currentTimeMillis()), Constants.SALT);
//将token更新到数据库中,每次登陆进去要将redis中的token清空,实现挤兑登录
User user = userMapper.selectById(userInfo.getCurrentUserInfo().getId());
String oldToken=user.getToken();
user.setToken(token);
userMapper.updateById(user);
// 当前用户所拥有角色代码
String roleCodes = userInfo.getRoleCodes();
// 生成jwt访问令牌
/*String jwt = Jwts.builder()
// 用户角色
.claim(Constants.ROLE_LOGIN, roleCodes)
// 主题 - 存用户名
.setSubject(authentication.getName())
// 过期时间
.setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * MyProperties.getAuth().getTokenExpireTime()))
// 加密算法和密钥
.signWith(SignatureAlgorithm.HS512, Constants.SALT)
.compact();*/
Map<String,Object> redisMap=new HashMap<>();
//将用户信息存入redis
redisMap.put(Constants.REDIS_KEY,userInfo);
log.info("jwt有效时间为: "+MyProperties.getAuth().getTokenExpireTime());
//token作为键存入redis
redisUtils.redisTimeSet(token,MyProperties.getAuth().getTokenExpireTime(),redisMap);
//登录成功后将原来的token清空
redisUtils.redisDel(oldToken);
userInfo.getCurrentUserInfo().setToken(token);
MyAuthenticationToken result = new MyAuthenticationToken(userInfo, userInfo.getAuthorities());
//return new UsernamePasswordAuthenticationToken(userInfo, password, userInfo.getAuthorities());
return result;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
注意
MyAuthenticationToken authRequest=(MyAuthenticationToken)authentication;
String curLoginType= MapUtils.getString(authRequest.getParam(),"curLoginType","-1");
通过强制转换就可以得到我自定义的那个类MyAuthenticationToken,就可以找到这个参数map了,很巧妙的解决了多个参数的传递问题
注意,本例有很多与本话题无关的代码,只需要细读关键代码即可!
附上前端postman传参:
{"username":"admin2","password":"123456","curLoginType":"1"}