美文网首页
spring-security-5.0版本之UsernamePa

spring-security-5.0版本之UsernamePa

作者: Aysn | 来源:发表于2018-06-22 21:03 被阅读0次

    首先,先上代码。

    package pn.empire.security.filter;
    
    import java.security.interfaces.RSAPrivateKey;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.ResourceBundle;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang3.StringUtils;
    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.util.Assert;
    
    import pn.empire.security.exception.CodeException;
    import pn.empire.security.exception.ForbidIPException;
    import pn.empire.util.NetWorkUtil;
    import pn.empire.util.RSAUtils;
    
    public class UsernamePasswordFilter
            extends org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter {
        // ~ Static fields/initializers
        // =====================================================================================
        // 用户名
        public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username";
        // 密码
        public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password";
        public static final String SPRING_SECURITY_FORM_REDERICT_KEY = "spring-security-redirect";
    
        private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
        private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
        private boolean postOnly = true;
    
        private String redirectParameter = SPRING_SECURITY_FORM_REDERICT_KEY;
        // ~ Methods
        // ========================================================================================================
    
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
                throws AuthenticationException {
            if (postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            }
            if (request == null) {
                throw new AuthenticationServiceException("request is null");
            }
            String username = obtainUsername(request);
            String password = obtainPassword(request);
            String code = request.getParameter("code");
            String sessionCode = (String) request.getSession(true).getAttribute("code");
            request.getSession(true).removeAttribute("code");
            if (sessionCode == null) {
                throw new CodeException("您的验证码已过期,请刷新验证码!");
            }
            // 验证码相关
            if (code == null) {
                throw new CodeException("请输入验证码");
            }
            if (!code.toLowerCase().equals(sessionCode.toLowerCase())) {
                throw new CodeException("请正确输入验证码!");
            }
            ResourceBundle resource = ResourceBundle.getBundle("forbidip");
            String forbidIpsStr = resource.getString("ip.address");
            List<String> forbidIps = new ArrayList<String>();
            if (forbidIpsStr != null && forbidIpsStr.indexOf(",") != -1) {
                String[] ips = forbidIpsStr.split(",");
                forbidIps = Arrays.asList(ips);
            }
            String ip = "";
            ip = NetWorkUtil.getIpAddress(request);
            if (ip.indexOf("非法地址:") != -1) {
                throw new ForbidIPException("非法IP");
            }
            if (forbidIps.size() > 0 && forbidIps.contains(ip)) {
                throw new ForbidIPException("非法IP");
            }
            RSAPrivateKey privateKey = (RSAPrivateKey) request.getSession().getAttribute("privateKey");
            try {
                String str = RSAUtils.decryptByPrivateKey(password, privateKey);
                password = StringUtils.reverse(str);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            String redirectUrl = obtainRedercitUrl(request);
            if (username == null) {
                username = "";
            }
    
            if (password == null) {
                password = "";
            }
            // 自定义回调URL,若存在则放入Session
            if (redirectUrl != null && !"".equals(redirectUrl)) {
                request.getSession().setAttribute("callCustomRediretUrl", redirectUrl);
            }
            username = username.trim();
            // 在这里处理密码或者用户名
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
        /**
         * Enables subclasses to override the composition of the password, such as by
         * including additional values and a separator.
         * <p>
         * This might be used for example if a postcode/zipcode was required in addition to
         * the password. A delimiter such as a pipe (|) should be used to separate the
         * password and extended value(s). The <code>AuthenticationDao</code> will need to
         * generate the expected password in a corresponding manner.
         * </p>
         *
         * @param request
         *            so that request attributes can be retrieved
         *
         * @return the password that will be presented in the <code>Authentication</code>
         *         request token to the <code>AuthenticationManager</code>
         */
        protected String obtainPassword(HttpServletRequest request) {
            return request.getParameter(passwordParameter);
        }
    
        /**
         * Enables subclasses to override the composition of the username, such as by
         * including additional values and a separator.
         *
         * @param request
         *            so that request attributes can be retrieved
         *
         * @return the username that will be presented in the <code>Authentication</code>
         *         request token to the <code>AuthenticationManager</code>
         */
        protected String obtainUsername(HttpServletRequest request) {
            return request.getParameter(usernameParameter);
        }
    
        /**
         * Provided so that subclasses may configure what is put into the authentication
         * request's details property.
         *
         * @param request
         *            that an authentication request is being created for
         * @param authRequest
         *            the authentication request object that should have its details
         *            set
         */
        protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
            authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        }
    
        /**
         * Sets the parameter name which will be used to obtain the username from the login
         * request.
         *
         * @param usernameParameter
         *            the parameter name. Defaults to "username".
         */
        public void setUsernameParameter(String usernameParameter) {
            Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
            this.usernameParameter = usernameParameter;
        }
    
        /**
         * Sets the parameter name which will be used to obtain the password from the login
         * request..
         *
         * @param passwordParameter
         *            the parameter name. Defaults to "password".
         */
        public void setPasswordParameter(String passwordParameter) {
            Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
            this.passwordParameter = passwordParameter;
        }
    
        /**
         * Defines whether only HTTP POST requests will be allowed by this filter. If set to
         * true, and an authentication request is received which is not a POST request, an
         * exception will be raised immediately and authentication will not be attempted. The
         * <tt>unsuccessfulAuthentication()</tt> method will be called as if handling a failed
         * authentication.
         * <p>
         * Defaults to <tt>true</tt> but may be overridden by subclasses.
         */
        public void setPostOnly(boolean postOnly) {
            this.postOnly = postOnly;
        }
    
        protected String obtainRedercitUrl(HttpServletRequest request) {
            return request.getParameter(redirectParameter);
        }
    
    }
    
    

    说一下变量:
    1.SPRING_SECURITY_FORM_USERNAME_KEY:这个静态常量定义了页面传递到后台中用户名的变量名。
    2.SPRING_SECURITY_FORM_PASSWORD_KEY:这个静态常量定义了页面传递到后台中用户密码的变量名。
    3.SPRING_SECURITY_FORM_REDERICT_KEY:这个静态常量定义了页面传递到后台中路径的变量名,因为是ajax登录,所以可以通过该变量控制登录成功后跳转至哪里。如果是表单登录,应该可以通过重定向来实现。
    核心方法attemptAuthentication:

            if (postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            }
    

    上面这一段代码控制了登录请求只能以post方式进行。

            String code = request.getParameter("code");
            String sessionCode = (String) request.getSession(true).getAttribute("code");
            request.getSession(true).removeAttribute("code");
            if (sessionCode == null) {
                throw new CodeException("您的验证码已过期,请刷新验证码!");
            }
            // 验证码相关
            if (code == null) {
                throw new CodeException("请输入验证码");
            }
            if (!code.toLowerCase().equals(sessionCode.toLowerCase())) {
                throw new CodeException("请正确输入验证码!");
            }
    

    上面代码简单实现了一个验证码校验功能,在访问登录页面时,后台生成验证码并存储到session中,之后取值移除并进行校验,此处抛出了自定义异常CodeException,目的是方便失败后进行异常捕捉,明确错误信息并提示给用户。

            ResourceBundle resource = ResourceBundle.getBundle("forbidip");
            String forbidIpsStr = resource.getString("ip.address");
            List<String> forbidIps = new ArrayList<String>();
            if (forbidIpsStr != null && forbidIpsStr.indexOf(",") != -1) {
                String[] ips = forbidIpsStr.split(",");
                forbidIps = Arrays.asList(ips);
            }
            String ip = "";
            ip = NetWorkUtil.getIpAddress(request);
            if (ip.indexOf("非法地址:") != -1) {
                throw new ForbidIPException("非法IP");
            }
            if (forbidIps.size() > 0 && forbidIps.contains(ip)) {
                throw new ForbidIPException("非法IP");
            }
    

    上面代码是通过读取配置文件进行ip限制,如果是当前ip被禁止则抛出自定义异常。
    当然请求的ip是从request中取得,此种获取ip的方法并不可靠,聊胜于无,如果大家有好的方法,欢迎指导。

           RSAPrivateKey privateKey = (RSAPrivateKey)request.getSession().getAttribute("privateKey");
            try {
                String str = RSAUtils.decryptByPrivateKey(password, privateKey);
                password = StringUtils.reverse(str);
            } catch (Exception e) {
                e.printStackTrace();
            }
    

    这一段代码是对前台传递过来的密码进行rsa解密,目前公钥私钥都是存储在session 中的,每个会话都是单独生成的,至于此种做法是否更加安全,待验证。

    至于redirectUrl,此属性是为了登录成功后配合ajax可以跳转到指定路径。

      setDetails(request, authRequest);
      return this.getAuthenticationManager().authenticate(authRequest);
    

    设置details,这里就是设置org.springframework.security.web.authentication.WebAuthenticationDetails实例到details中
    之后会通过UserDetailServiceImpl进行校验。
    以上便是UsernamePasswordFilter的主要内容了。
    之后会写UserDetailServiceImpl。

    相关文章

      网友评论

          本文标题:spring-security-5.0版本之UsernamePa

          本文链接:https://www.haomeiwen.com/subject/xghhzxtx.html