如有不足,敬请各位提出批评,定会改正。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 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、前台请求服务器的公钥,传输到前台,因为公钥是公开的,使用公钥在前台加密用户登录注册或者其他保密的信息,将公钥加密的密文传输到服务器。服务器通过与公钥对应的私钥,解密数据库的密文和传输过来的密文,将解密后的字符相比较,再将确认信息返回到前台,进行下一步的操作。