RSA加密算法及其与SpringMVC集成

如有不足,敬请各位提出批评,定会改正。THX!

 

本文介绍的是RSA加密算法+Spring Security在SpringMVC中的集成使用。

Spring Security是什么? 

引用:
  Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
  Spring Security以前叫做acegi,是后来才成为Spring的一个子项目,也是目前最为流行的一个安全权限管理框架,它与Spring紧密结合在一起。
  Spring Security关注的重点是在企业应用安全层为您提供服务,你将发现业务问题领域存在着各式各样的需求。银行系统跟电子商务应用就有很大的不同。电子商务系统与企业销售自动化工具又有很大不同。这些客户化需求让应用安全显得有趣,富有挑战性而且物有所值。Spring Security为基于J2EE的企业应用软件提供了一套全面的安全解决方案。

学习Spring Security的网址http://www.iteye.com/blogs/subjects/spingsecurity3inside。

Spring-Security 自带的加密算法有

- bcrypt
- plaintext
- sha
- sha-256
- md5
- md4
- {sha}
- {ssha}

 由于md5加密算法,使用较为普遍,但是可以通过碰撞的方式破解,不采用。

 RSA是什么?      

      同RSA(Ron Rivest,Adi Shamir,Len Adleman三位天才的名字)一样,ECC(Elliptic Curves Cryptography,椭圆曲线密码编码学)也属于公开密钥算法。但是ECC算法在jdk1.5后加入支持,目前仅仅只能完成密钥的生成与解析。

  RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。今天只有短的RSA钥匙才可能被强力方式解破。到2008年为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被解破的。但在分布式计算和量子计算机理论日趋成熟的今天,RSA加密安全性受到了挑战。RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。

RSA密钥长度随着保密级别提高,增加很快。下表列出了对同一安全级别所对应的密钥长度。

保密级别
对称密钥长度(bit)
RSA密钥长度(bit)
ECC密钥长度(bit)
保密年限
80
80
1024
160
2010
112
112
2048
224
2030
128
128
3072
256
2040
192
192
7680
384
2080
256
256
15360
512
2120

 

如何实现的RSA+Spring Security 在Spring MVC中的实现,直接上web.xml

 xml中的com.user.sec.MyHttpSessionEventPublisher
为自定义的org.springframework.security.web.session.HttpSessionEventPublisher  两者使用其一。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" metadata-complete="false" version="2.5">
 <!--配置上下文参数,指定spring配置文件的位置 -->  
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/applicationContext-*.xml</param-value> </context-param>
<!--Spring Security 过滤器-->
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
<!--Spring 字符集过滤器 -->
<!--包含设置两个参数encoding和forceEncoding--> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>*</url-pattern> </filter-mapping> <filter> <filter-name>hibernateFilter</filter-name> <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class> <init-param> <param-name>excludeSuffixs</param-name> <param-value>js,css,jpg,gif</param-value> </init-param> </filter> <filter-mapping> <filter-name>hibernateFilter</filter-name> <url-pattern>*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>com.user.sec.MyHttpSessionEventPublisher</listener-class> </listener> <!-- <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener> --> <listener> <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class> </listener> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>WEB-INF/classes/properties/log4j.properties</param-value> </context-param> <context-param> <param-name>log4jRefreshInterval</param-name> <param-value>60000</param-value> </context-param> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/applicationContext-springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <servlet-name>springmvc</servlet-name> </filter-mapping> <session-config> <session-timeout>30</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <error-page> <error-code>500</error-code> <location>/500.html</location> </error-page> </web-app>

applicationContext-security.xml

  Spring Security采用就近原则,有多个约束时,从上至下只要找到第一条满足就返回,因此因该将最严格的约束放在最前面,而将最宽松的约束放在最后面.auto-config属性可以让spring security为我们自动配置几种常用的权限控制机制,包括form,anonymous, rememberMe等。当然你也可以手工配置。例如:<http auto-config="true">  

对于拦截pattern的设置,具体如下:

/  所有带/的请求

/* 代表这个域下面的请求  例如:/user/xxx 这种会被拦截  但是不会拦截 /user/xxx/xxx

/** 代表跨域请求  例如:/user/xxx  和 /user/xxx/xxx都会被拦截

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">

    <global-method-security secured-annotations="enabled" />
  
<!--配置不过滤的资源,包括登陆请求,静态资源访问,注册页面,获取登陆注册验证码,当然也包括此篇文章所涉及从后台获取的公钥接口-->
   <http pattern="/resources/**" security="none" /> <http pattern="/login.html" security="none" /> <http pattern="/register.html" security="none" /> <http pattern="/admin/user/getPairKey" security="none"
<http use-expressions="true" auto-config="true"> <!--允许登录用户操作 --> <intercept-url pattern="/pages/**" access="permitAll" /> <intercept-url pattern="/**" access="isAuthenticated()" /> <!--所有用户均可使用 --> <!-- <intercept-url pattern="/**" access="permitAll" /> --> <!-- 允许匿名用户访问 --> <!-- <intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY" /> --> <!-- <intercept-url pattern="/" access="ROLE_ANONYMOUS" /> --> <!-- 验证失败页面 --> <form-login login-page="/login.html" authentication-failure-url="/login.html?error=true" login-processing-url="/j_spring_security_check" default-target-url="/index.html" always-use-default-target="true" /> <!-- 注销页面 --> <logout logout-success-url="/login.html" /> <!-- 记住登陆 --> <remember-me key="mpbuser" user-service-ref="userDetailService" /> <session-management invalid-session-url="/login.html" session-authentication-error-url="/login.html"> <concurrency-control max-sessions="100" error-if-maximum-exceeded="false" expired-url="/login.html" /> </session-management> <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myDefineFilter" /> <!-- 验证权限不足处理类 --> <access-denied-handler ref="accessDeniedHandler" /> <http-basic /> </http> <beans:bean id="encoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder" /> <beans:bean id="RSAEncoder" class="com.common.component.util.RSAPasswordEncoder" /> <authentication-manager alias="authenticationManager" erase-credentials="false"> <authentication-provider user-service-ref="userDetailService"> <password-encoder ref="RSAEncoder" /> </authentication-provider> <!--自定义Provider --> <!-- <authentication-provider ref="myAuthenticationProvider"> </authentication-provider> --> </authentication-manager> <beans:bean id="myDefineFilter" class="com.user.sec.MyFilterSecurityInterceptor"> <beans:property name="authenticationManager" ref="authenticationManager" /> <!-- <beans:property name="accessDecisionManager" ref="accessDecisionManager" /> --> <beans:property name="securityMetadataSource" ref="databaseDefinitionSource" /> <beans:property name="accessDecisionManager" ref="myAccessDecisionManager" /> </beans:bean> <!-- <beans:bean id="myAuthenticationProvider" class="crowdfunding.user.sec.MyAuthenticationProvider"/> --> <beans:bean id="databaseDefinitionSource" class="com.user.sec.DefinitionSourceFactoryBean"> <beans:constructor-arg ref="resourceDetailService" /> <!-- <beans:constructor-arg ref="userDetailService" /> --> </beans:bean> <beans:bean id="myAccessDecisionManager" class="com.user.sec.MyAccessDecisionManager"> </beans:bean> <beans:bean id="accessDeniedHandler" class="com.user.sec.MyAccessDeniedHandler" /> </beans:beans>

 RSAPasswordEncoder.Java

package com.common.component.util;

import java.net.URLDecoder;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.encoding.PasswordEncoder;

import Decoder.BASE64Decoder;
import Decoder.BASE64Encoder;
import com.common.component.util.MpbSecureRSAUtil;

/*******************************************************
 * Description:自定义RSA处理类 
 *                     如需增加Spring Security 另外的加密方式,重新定义此类<br/>
 ********************************************************/
public class RSAPasswordEncoder implements PasswordEncoder {

    /*****************************************
     * Description:明文加密 <br/>
     * @return
     * @param rawPass
     *            密码
     ******************************************/
    public String encodePassword(String rawPass, Object salt) {

        String encoder = "";
        try {
            encoder = MpbSecureRSAUtil.decryptString(rawPass);
        } catch (Exception e) {

            e.printStackTrace();
        }
        return encoder;
    }

    /*****************************************
     * Description:验证密码是否有效主要是此方法的使用<br/>
     * 
     * @return boolean
     ******************************************/
    public boolean isPasswordValid(String encPass, String rawPass, Object salt) {

        if ( !MpbStringUtil.isNotBlank(encPass) || encPass.length() == 0) {
            return false;
        }
        String decPsw = MpbSecureRSAUtil.decryptString(encPass);
        String inpdecPsw = MpbSecureRSAUtil.decryptStringByJs(rawPass);
        try {
            decPsw = URLDecoder.decode(decPsw, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
            return  false;
        }
        return inpdecPsw.equals(decPsw);
    }

    /*****************************************
     * Description:私钥解密 <br/>
     * 
     * @return
     ******************************************/
    public String decryptPassword(String rawPass, Object salt) {

        String encoder = "";
        try {
            encoder = MpbSecureRSAUtil.decryptString(rawPass);
        } catch (Exception e) {

            e.printStackTrace();
        }
        return encoder;
    }

    /*****************************************
     * Description:Base64解密 <br/>
     * @param key
     * @return
     * @throws Exception
     ******************************************/
    public static byte[] decoderBase64(String key) throws Exception {
        return (new BASE64Decoder()).decodeBuffer(key);
    }

    /*****************************************
     * Description:Base64加密 <br/>
     * 
     * @param key
     * @return
     * @throws Exception
     ******************************************/
    public static String encoderBase64(byte[] key) throws Exception {
        return (new BASE64Encoder()).encodeBuffer(key);
    }

}

 login.html

<script type="text/javascript" src="resources/js/RSA/security.js"></script>

 

    function mysubmit() {
         if ($("input[name='_spring_security_remember_me']").attr("checked") == "checked") {
            //如果记住用户名和密码框被选中则执行remenber()方法
            remember();
        }else{
            //如果未选中则清空cookie
            $.mpbSetCookie("EBS_ISCHECKED", "");
        } 
//从后台获取公钥并加密,将加密后的密文传输到服务器处理
        $.mpbPost(
                "/admin/user/getPairKey", 
                {
                    "_method" : "GET"
                }, 
                function(data) {
        
                    var modulus = data['modul'];
                    var exponent = data['exponent'];
                    var key = RSAUtils.getKeyPair(exponent, '', modulus);
                    var pwd = $("#psw").val();
                    pwd = encodeURIComponent(pwd);
                    pwd = RSAUtils.encryptedString(key, pwd);
                    //$("#psw").attr("value",pwd);
                    $("input[name='j_password']").val(pwd);
                    $("#myform").submit();
                });
        
        
    }

Controler.Java

@ResponseBody
    @RequestMapping(value = "/getPairKey", method = RequestMethod.GET)
    public Map<?,?> getKey() throws Exception {
        RSAPublicKey publicKey = MpbSecureRSAUtil.getDefaultPublicKey();

        Map<String, String> key = new HashMap<String,String>();

        key.put("modul",
                new String(Hex.encodeHex(publicKey.getModulus().toByteArray())));
        key.put("exponent",
                new String(Hex.encodeHex(publicKey.getPublicExponent()
                        .toByteArray())));

        return key;
    }

 文件地址:

https://files.cnblogs.com/files/Sonet-life/security.js

https://files.cnblogs.com/files/Sonet-life/MpbSecureRSAUtil.rar

整个加密的思路:

1、项目在启动时就检查是否生成密钥对文件,没有就强制生成新的,记住利用RSA注册的用户在数据库保存的是加密后的密文,RSA密钥对文件要保证一致性,如果密钥对文件生成了新的,而且你没备份之前的密钥对文件,将会造成不可挽回的后果。最好的方法,通过对MpbSecureRSAUtil中密钥对文件的生成路径设置成硬盘下的,不要放到Tomcat服务器中,否则每次你删除项目的时候,或者重新部署都会造成密钥对文件的重新生成。或者每次都用旧的替换新生成的,保持可用性。

2、前台请求服务器的公钥,传输到前台,因为公钥是公开的,使用公钥在前台加密用户登录注册或者其他保密的信息,将公钥加密的密文传输到服务器。服务器通过与公钥对应的私钥,解密数据库的密文和传输过来的密文,将解密后的字符相比较,再将确认信息返回到前台,进行下一步的操作。

posted @ 2015-07-15 09:59  Sonet  阅读(8598)  评论(0编辑  收藏  举报