我是Spring的新手(以及一般的Web开发),但我设法创建了一个简单的Web应用程序,现在我正在尝试为其添加身份验证支持,以便用户需要登录访问其功能。
我正在使用Spring MVC 3.2.2,Spring Security 3.1.4和Tomcat 7。
出于测试目的,我能够使用硬编码用户添加身份验证支持,一切正常。现在,我必须使用现有的PostgreSQL数据库来验证用户。我想强调一下,我的应用程序不支持创建用户;用户已存储在数据库中。
以下是问题:
(1)应用程序必须根据PostgreSQL数据库对用户进行身份验证,我无法以任何方式对其进行修改
(2)使用crypt('纯文本密码',gen_salt(md5))对数据库中的密码进行哈希处理。
(3)由于我大量使用了注释和xml配置,基本上大部分的工作都是由Spring完成的,这意味着很多事情都在幕后进行,我不知道。结果,我现在卡住设置密码编码器必须用来验证用户的盐。这样的salt必须是存储在数据库中的已经散列的密码。
问题是:
如何告诉Spring Security使用存储在数据库中的散列密码作为密码编码器的盐?有什么方法可以从xml中执行此操作,还是需要继续执行某些类?
我在网上进行了大量搜索,到目前为止我发现的所有示例与我所做的大不相同,主要是因为在示例中,大部分功能都是由开发人员实现的,而不是主要依赖于构建-in功能。
这是 security.xml 文件:
<http use-expressions="true">
<intercept-url pattern="/resources/images/**" access="permitAll" requires-channel="any"/>
<intercept-url pattern="/resources/css/**" access="permitAll" requires-channel="any"/>
<intercept-url pattern="/login*" access="permitAll" requires-channel="any"/>
<intercept-url pattern="/**" access="hasAnyRole('ROLE_Processer', 'ROLE_Verifier', 'ROLE_Approver', 'ROLE_Supervisor', 'ROLE_Admin')" requires-channel="any"/>
<session-management>
<concurrency-control max-sessions="1" expired-url=/login?expired=true" session-registry-alias="sessionRegistry"/>
</session-management>
<form-login
login-page="/login"
default-target-url="/"
always-use-default-target="true"
authentication-failure-url="/login?error=true"/>
<logout logout-success-url="/login" invalidate-session="true" delete-cookies="JSESSIONID"/>
</http>
<authentication-manager>
<authentication-provider>
<jdbc-user-service
data-source-ref="dataSource"
users-by-username-query="SELECT id AS username, password, enabled FROM users WHERE id=?;"
authorities-by-username-query="SELECT id AS username, role FROM users WHERE id=?;"
role-prefix="ROLE_"/>
<password-encoder hash="md5">
<salt-source ???/>
</password-encoder>
</authentication-provider>
</authentication-manager>
这是 web.xml 文件的相关摘录:
<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>
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
这是 application-context.xml 文件的相关摘录:
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<beans:property name="driverClass" value="org.postgresql.Driver"/>
<beans:property name="url" value="jdbc:postgresql://host:port/database"/>
<beans:property name="username" value="username"/>
<beans:property name="password" value="password"/>
</beans:bean>
在实施方面,这是唯一相关的控制器 LoginController.java (所有其他控制器处理应用程序提供的实际功能):
@Controller
公共类LoginController {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
/**
* Displays the Login form.
*/
@RequestMapping(value="/login", method=RequestMethod.GET)
public String displayLoginForm(@RequestParam(value="error", required=false) String error, @RequestParam(value="expired", required=false) String expired, Model model) {
LOGGER.info("Displaying Login form:: displayLoginForm(Model)");
if (error != null) {
LOGGER.info("Invalid username or password.");
model.addAttribute("error", "Invalid username or password.");
}
if (expired != null) {
LOGGER.info("Session has been expired (possibly due to multiple concurrent logins being attempted as the same user)");
model.addAttribute("expired", "Session has been expired (possibly due to multiple concurrent logins being attempted as the same user).");
}
return "login";
}}
任何指针都将非常感激。我想提前感谢你们的时间和帮助。
修改
我试过用
<password-encoder hash="md5">
<salt-source user-property="password"/>
</password-encoder>
因为,从我通过阅读documentation的理解,这是存储在数据库中的用户密码。但是,它不起作用。每次我尝试登录时,都会收到无效的凭据消息;但是,凭证确实有效。
此 page 的哈希和身份验证部分中描述的任何事件可能都是它无效的原因。我想知道如何按照建议编写适当的测试。
另一个编辑:
作为测试,我注释掉了密码编码器元素并尝试使用纯文本密码进行身份验证(即,我创建了一个测试用户并在数据库中存储了纯文本密码)。有效。因此,问题肯定在于Spring Security编码用户输入的密码的方式,这与存储在数据库中的散列密码不匹配。
答案 0 :(得分:3)
嗯,为了解决我的问题,我实现了一个自定义密码编码器。基本上,我覆盖了PasswordEncoder接口的匹配方法:
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
//encoding raw password: the given encodedPassword serves as the salt
String sql = "SELECT crypt(?, ?) as hashedpwd;";
String hashedPassword = aJdbcTemplate.query(sql, new String[] {rawPassword.toString(), encodedPassword}, new ResultSetExtractor<String>() {
@Override
public String extractData(ResultSet rs) throws SQLException, DataAccessException {
rs.next();
return rs.getString("hashedpwd");
}
});
return encodedPassword.equals(hashedPassword.toString());
}