SpringSecurity登录表单添加额外自定义字段
一、阐述
在使用Spring Security框架过程中,经常会在登录验证时,附带增加额外的数据,如验证码、系统类型等。我之前写过一篇实现添加验证码的方式,但是code参数是验证用户密码时在request中获取,就会在分布式时会有问题,因此这篇正好也作为补充。
本文有两种不同的方法,以展示框架的多功能性以及我们可以使用它的灵活方式。当然,第二种也是建立在第一种的基础之上,对Security的运用更加深入。
1.1 重点必看
本文是建立在此基础上的调整
Springboot + Spring Security 前后端分离权限控制
二、实现
首先要声明一下,因为实现将登陆接口由表单改成了json形式,若是在json中添加新参数,使用 request.getParameter(“param”); 的方法已经不可再用。但是,在URL上拼接新参数 ?param=1234 就可以采用request方式获取。
2.1 法一:拼接实现
很好理解,AuthenticationProvider在接收后只有username与password,别的字段是没有地方使用的。本方法其实就是在传入之后,在继承UsernamePasswordAuthenticationFilter时,将自定义参数与username或password进行拼接,使用的时候再拆开。
本方法取巧,但是实现起来还是很方便的。
具体实现参考:Springboot + Spring Security 前后端分离权限控制,下边只是部分改写了一点点。
2.1.1 登录过滤,处理json
继承UsernamePasswordAuthenticationFilter,修改参数。
package net.cnki.security.filter;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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 com.fasterxml.jackson.databind.ObjectMapper;
import net.cnki.security.hander.MyAuthenctiationFailureHandler;
import net.cnki.security.verify.MyAuthenticationToken;
/**
*
* @author ZhiPengyu
* @ClassName: [MyLoginAuthenticationFilter.java]
* @Description: [登录过滤,处理json]
* @CreateDate: [2020年8月13日 下午5:22:12]
*/
public class MyLoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;
@Override
public Authentication attemptAuthentication(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws AuthenticationException {
logger.info("登录过滤,处理json!");
HttpServletRequest request= (HttpServletRequest) servletRequest;
HttpServletResponse response= (HttpServletResponse) servletResponse;
if (!request.getMethod().equals("POST")) {//必须post登录
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
//如果是application/json类型,做如下处理
if(request.getContentType() != null && (request.getContentType().equals("application/json;charset=UTF-8")||request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE))){
//以json形式处理数据
String username = null;
String password = null;
String sysname = null;
String orgname = null;
try {
//将请求中的数据转为map
@SuppressWarnings("unchecked")
Map<String,String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
username = map.get("username");
password = map.get("password");
sysname = map.get("sysname");
orgname = map.get("orgname");
} catch (IOException e) {
e.printStackTrace();
}
username = username == null ? "":username.trim();
password = password == null ? "":password.trim();
sysname = sysname == null ? "":sysname.trim();
orgname = orgname == null ? "":orgname.trim();
//-------------------------此处进行拼接。--------------------------
username = username + "-"+sysname +"-"+orgname ;
//---------------------------------------------------
MyAuthenticationToken authRequest = new MyAuthenticationToken(username, password);
setDetails(servletRequest, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
//否则使用官方默认处理方式
return super.attemptAuthentication(request, response);
}
}
2.1.2 用户验部分
此处就更简单了,获取到之后直接切割就可以了。
package net.cnki.security;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import net.cnki.common.controller.UserLogBaseController;
@Component
public class MyAuthenticationProvider extends UserLogBaseController implements AuthenticationProvider {
Logger logger = LoggerFactory.getLogger(MyAuthenticationProvider.class);
@Autowired
private MyUserDetailService userService;
@Autowired
HttpServletRequest httpServletRequest;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 自定义验证方式
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String usernameparam = authentication.getName();
String password = (String) authentication.getCredentials();
//-------------------------------------------------------------------
String[] usern = usernameparam.split("-");
String username = usern[0];
String sysname = usern[1];
String orgname = usern[2];
//-------------------------------------------------------------------
UserDetails user = userService.loadUserByUsername(username);
//加密过程在这里体现
logger.info("结果CustomUserDetailsService后,已经查询出来的数据库存储密码:" + user.getPassword());
if (!passwordEncoder().matches(password, user.getPassword())) {
logger.info("登录用户密码错误!");
throw new DisabledException("登录用户密码错误!");
}
String requestCode = httpServletRequest.getParameter("vercode");
HttpSession session = httpServletRequest.getSession();
String saveCode = (String) session.getAttribute("RANDOMVALIDATECODEKEY");//captcha
//获取到session验证码后随时清除
if(!StringUtils.isEmpty(saveCode)) {
session.removeAttribute("RANDOMVALIDATECODEKEY");//captcha
}
logger.info("requestCode:"+requestCode+",saveCode:"+saveCode);
if(StringUtils.isEmpty(saveCode) || StringUtils.isEmpty(requestCode) || !requestCode.equals(saveCode)) {
logger.info("图片验证码错误!");
throw new DisabledException("图形验证码错误!");
}
logger.info("登录成功");
addUserLog("用户登录", "登录", 1, username);
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}
@Override
public boolean supports(Class<?> arg0) {
return true;
}
}
2.2 法二:重写实现
同样的方式,与上一方式一样,只不过是修改更多的重写,整体上没啥变化,相对来说更高级一些。添加参数的同时,在接受上也改写。
2.2.1 继承 UsernamePasswordAuthenticationToken
package net.cnki.security.verify;
import java.util.Collection;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
public class MyAuthenticationToken extends UsernamePasswordAuthenticationToken {
/**
*
*/
private static final long serialVersionUID = 2112284640094900016L;
private String sysname;
private String orgname;
public MyAuthenticationToken(Object principal, Object credentials, String sysname, String orgname) {
super(principal, credentials);
this.sysname = sysname;
this.orgname = orgname;
super.setAuthenticated(false);
}
public MyAuthenticationToken(Object principal, Object credentials, String sysname, String orgname,
Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
this.sysname = sysname;
this.orgname = orgname;
super.setAuthenticated(true);
}
public String getSysname() {
return this.sysname;
}
public String getOrgname() {
return orgname;
}
}
2.2.2 实现 AuthenticationProvider
package net.cnki.security.verify;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONObject;
import cn.hutool.core.lang.Validator;
import cn.hutool.http.HttpResponse;
import net.cnki.api.service.CnkiService;
import net.cnki.common.Const;
import net.cnki.modules.bean.SysLog;
import net.cnki.modules.bean.SysRole;
import net.cnki.modules.bean.SysUser;
import net.cnki.modules.service.SysLogService;
import net.cnki.modules.service.SysRoleService;
import net.cnki.modules.service.SysUserService;
import net.cnki.redis.JedisUtils;
import net.cnki.redis.RedisConstants;
import net.cnki.util.AESUtil;
import net.cnki.util.HttpContextUtils;
import net.cnki.util.IPUtils;
/**
*
* @author ZhiPengyu
* @ClassName: [MyAuthenticationProvider.java]
* @Description: [登录校验-验证部分]
* @CreateDate: [2020年8月11日 上午10:47:30]
*/
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
Logger logger = LoggerFactory.getLogger(MyAuthenticationProvider.class);
@Autowired
JedisUtils jedisUtils;
@Autowired
CnkiService cnkiService;
@Autowired
MyUserDetailService userService;
@Autowired
SysUserService sysUserService;
@Autowired
SysRoleService roleService;
@Autowired
SysLogService sysLogService;
@Value("${redis.prefixName}")
private String prefixName;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 去除密码验证并授权
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.info("登录授权部分!");
//---------------------获取参数--------------
MyAuthenticationToken auth = (MyAuthenticationToken) authentication;
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
String username = auth.getName();
String password = (String) auth.getCredentials();
String sysname = auth.getSysname();
String orgname = auth.getOrgname();
//------------------------自定义登陆过程--------------
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, user.getPassword(), authorities);//密码为未加密
}
@Override
public boolean supports(Class<?> arg0) {
return true;
}
}
2.2.3 继承 UsernamePasswordAuthenticationFilter
package net.cnki.security.filter;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.cnki.security.hander.MyAuthenctiationFailureHandler;
import net.cnki.security.verify.MyAuthenticationToken;
/**
*
* @author ZhiPengyu
* @ClassName: [MyLoginAuthenticationFilter.java]
* @Description: [登录过滤,处理json]
* @CreateDate: [2020年8月13日 下午5:22:12]
*/
public class MyLoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;
@Override
public Authentication attemptAuthentication(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws AuthenticationException {
logger.info("登录过滤,处理json!");
HttpServletRequest request= (HttpServletRequest) servletRequest;
HttpServletResponse response= (HttpServletResponse) servletResponse;
if (!request.getMethod().equals("POST")) {//必须post登录
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
//如果是application/json类型,做如下处理
if(request.getContentType() != null && (request.getContentType().equals("application/json;charset=UTF-8")||request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE))){
//以json形式处理数据
String username = null;
String password = null;
String sysname = null;
String orgname = null;
try {
//将请求中的数据转为map
@SuppressWarnings("unchecked")
Map<String,String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
username = map.get("username");
password = map.get("password");
sysname = map.get("sysname");
orgname = map.get("orgname");
} catch (IOException e) {
e.printStackTrace();
}
username = username == null ? "":username.trim();
password = password == null ? "":password.trim();
sysname = sysname == null ? "":sysname.trim();
orgname = orgname == null ? "":orgname.trim();
MyAuthenticationToken authRequest = new MyAuthenticationToken(username, password,sysname,orgname);
setDetails(servletRequest, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
//否则使用官方默认处理方式
return super.attemptAuthentication(request, response);
}
}