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;
    }

 文件地址:

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

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

整個加密的思路:

1、項目在啟動時就檢查是否生成密鑰對文件,沒有就強制生成新的,記住利用RSA注冊的用戶在數據庫保存的是加密后的密文,RSA密鑰對文件要保證一致性,如果密鑰對文件生成了新的,而且你沒備份之前的密鑰對文件,將會造成不可挽回的后果。最好的方法,通過對MpbSecureRSAUtil中密鑰對文件的生成路徑設置成硬盤下的,不要放到Tomcat服務器中,否則每次你刪除項目的時候,或者重新部署都會造成密鑰對文件的重新生成。或者每次都用舊的替換新生成的,保持可用性。

2、前台請求服務器的公鑰,傳輸到前台,因為公鑰是公開的,使用公鑰在前台加密用戶登錄注冊或者其他保密的信息,將公鑰加密的密文傳輸到服務器。服務器通過與公鑰對應的私鑰,解密數據庫的密文和傳輸過來的密文,將解密后的字符相比較,再將確認信息返回到前台,進行下一步的操作。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM