SpringBoot整合SpringSecurity登录表单添加额外自定义字段

10 篇文章 2 订阅
7 篇文章 1 订阅

一、阐述

在使用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);
    }

}

三、结果截图

在这里插入图片描述

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值