问题
I am using grails 2.1.0. I have installed spring-security-core plugin.
When I create user it is creating it. But when I try to login then it shows:
"Sorry, we were not able to find a user with that username and password."
And there is also another fact that is when I use the same password for different user it does not save password with similar encoded value like for user 1 I have used password 123 which is saved in database like this
d535ce213a0e8e4f9e724af47c46eea409ef401c03617b749da618a82890d743
and for user 2 I also used password 123 and this time it is saved like this
0849ea79a2c1bca057ded06c3053fb5bc5d7ba52b50982e73e44894d4f3e0aa6
I don't understand. Can anyone please help me on this ?
my config.groovy >>>
// locations to search for config files that get merged into the main config;
// config files can be ConfigSlurper scripts, Java properties files, or classes
// in the classpath in ConfigSlurper format
// grails.config.locations = [ "classpath:${appName}-config.properties",
// "classpath:${appName}-config.groovy",
// "file:${userHome}/.grails/${appName}-config.properties",
// "file:${userHome}/.grails/${appName}-config.groovy"]
// if (System.properties["${appName}.config.location"]) {
// grails.config.locations << "file:" + System.properties["${appName}.config.location"]
// }
grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination
grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
grails.mime.use.accept.header = false
grails.mime.types = [
all: '*/*',
atom: 'application/atom+xml',
css: 'text/css',
csv: 'text/csv',
form: 'application/x-www-form-urlencoded',
html: ['text/html','application/xhtml+xml'],
js: 'text/javascript',
json: ['application/json', 'text/json'],
multipartForm: 'multipart/form-data',
rss: 'application/rss+xml',
text: 'text/plain',
xml: ['text/xml', 'application/xml']
]
// URL Mapping Cache Max Size, defaults to 5000
//grails.urlmapping.cache.maxsize = 1000
// What URL patterns should be processed by the resources plugin
grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*']
// The default codec used to encode data with ${}
grails.views.default.codec = "none" // none, html, base64
grails.views.gsp.encoding = "UTF-8"
grails.converters.encoding = "UTF-8"
// enable Sitemesh preprocessing of GSP pages
grails.views.gsp.sitemesh.preprocess = true
// scaffolding templates configuration
grails.scaffolding.templates.domainSuffix = 'Instance'
// Set to false to use the new Grails 1.2 JSONBuilder in the render method
grails.json.legacy.builder = false
// enabled native2ascii conversion of i18n properties files
grails.enable.native2ascii = true
// packages to include in Spring bean scanning
grails.spring.bean.packages = []
// whether to disable processing of multi part requests
grails.web.disable.multipart=false
// request parameters to mask when logging exceptions
grails.exceptionresolver.params.exclude = ['password']
// configure auto-caching of queries by default (if false you can cache individual queries with 'cache: true')
grails.hibernate.cache.queries = false
environments {
development {
grails.logging.jul.usebridge = true
}
production {
grails.logging.jul.usebridge = false
// TODO: grails.serverURL = "http://www.changeme.com"
}
}
// log4j configuration
log4j = {
// Example of changing the log pattern for the default console appender:
//
//appenders {
// console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n')
//}
error 'org.codehaus.groovy.grails.web.servlet', // controllers
'org.codehaus.groovy.grails.web.pages', // GSP
'org.codehaus.groovy.grails.web.sitemesh', // layouts
'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
'org.codehaus.groovy.grails.web.mapping', // URL mapping
'org.codehaus.groovy.grails.commons', // core / classloading
'org.codehaus.groovy.grails.plugins', // plugins
'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
'org.springframework',
'org.hibernate',
'net.sf.ehcache.hibernate'
}
// Added by the Spring Security Core plugin:
grails.plugins.springsecurity.userLookup.userDomainClassName = 'common.auth.User'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'common.auth.UserAuthority'
grails.plugins.springsecurity.authority.className = 'common.auth.Authority'
my login controller >>>
import grails.converters.JSON
import javax.servlet.http.HttpServletResponse
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
import org.springframework.security.authentication.AccountExpiredException
import org.springframework.security.authentication.CredentialsExpiredException
import org.springframework.security.authentication.DisabledException
import org.springframework.security.authentication.LockedException
import org.springframework.security.core.context.SecurityContextHolder as SCH
import org.springframework.security.web.WebAttributes
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
class LoginController {
/**
* Dependency injection for the authenticationTrustResolver.
*/
def authenticationTrustResolver
/**
* Dependency injection for the springSecurityService.
*/
def springSecurityService
/**
* Default action; redirects to 'defaultTargetUrl' if logged in, /login/auth otherwise.
*/
def index = {
if (springSecurityService.isLoggedIn()) {
redirect uri: SpringSecurityUtils.securityConfig.successHandler.defaultTargetUrl
} else {
redirect action: 'auth', params: params
}
}
/**
* Show the login page.
*/
def auth = {
def config = SpringSecurityUtils.securityConfig
if (springSecurityService.isLoggedIn()) {
redirect uri: config.successHandler.defaultTargetUrl
return
}
String view = 'auth'
String postUrl = "${request.contextPath}${config.apf.filterProcessesUrl}"
render view: view, model: [postUrl: postUrl,
rememberMeParameter: config.rememberMe.parameter]
}
/**
* The redirect action for Ajax requests.
*/
def authAjax = {
response.setHeader 'Location', SpringSecurityUtils.securityConfig.auth.ajaxLoginFormUrl
response.sendError HttpServletResponse.SC_UNAUTHORIZED
}
/**
* Show denied page.
*/
def denied = {
if (springSecurityService.isLoggedIn() &&
authenticationTrustResolver.isRememberMe(SCH.context?.authentication)) {
// have cookie but the page is guarded with IS_AUTHENTICATED_FULLY
redirect action: 'full', params: params
}
}
/**
* Login page for users with a remember-me cookie but accessing a IS_AUTHENTICATED_FULLY page.
*/
def full = {
def config = SpringSecurityUtils.securityConfig
render view: 'auth', params: params,
model: [hasCookie: authenticationTrustResolver.isRememberMe(SCH.context?.authentication),
postUrl: "${request.contextPath}${config.apf.filterProcessesUrl}"]
}
/**
* Callback after a failed login. Redirects to the auth page with a warning message.
*/
def authfail = {
def username = session[UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY]
String msg = ''
def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
if (exception) {
if (exception instanceof AccountExpiredException) {
msg = g.message(code: "springSecurity.errors.login.expired")
} else if (exception instanceof CredentialsExpiredException) {
msg = g.message(code: "springSecurity.errors.login.passwordExpired")
} else if (exception instanceof DisabledException) {
msg = g.message(code: "springSecurity.errors.login.disabled")
} else if (exception instanceof LockedException) {
msg = g.message(code: "springSecurity.errors.login.locked")
} else {
msg = g.message(code: "springSecurity.errors.login.fail")
}
}
if (springSecurityService.isAjax(request)) {
render([error: msg] as JSON)
} else {
flash.message = msg
redirect action: 'auth', params: params
}
}
/**
* The Ajax success redirect url.
*/
def ajaxSuccess = {
render([success: true, username: springSecurityService.authentication.name] as JSON)
}
/**
* The Ajax denied redirect url.
*/
def ajaxDenied = {
render([error: 'access denied'] as JSON)
}
}
my authority.groovy >>>
package common.auth
class Authority {
String authority
static mapping = {
cache true
}
static constraints = {
authority blank: false, unique: true
}
}
my user domain.groovy where my user will be saved >>>
package common.auth
class User {
transient springSecurityService
String realname
String username
String password
String designation
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static constraints = {
username blank: false, unique: true
password blank: false
}
static mapping = {
password column: '`password`'
}
Set<Authority> getAuthorities() {
UserAuthority.findAllByUser(this).collect { it.authority } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
}
my userauthority.groovy >>>
package common.auth
import org.apache.commons.lang.builder.HashCodeBuilder
class UserAuthority implements Serializable {
User user
Authority authority
boolean equals(other) {
if (!(other instanceof UserAuthority)) {
return false
}
other.user?.id == user?.id &&
other.authority?.id == authority?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (user) builder.append(user.id)
if (authority) builder.append(authority.id)
builder.toHashCode()
}
static UserAuthority get(long userId, long authorityId) {
find 'from UserAuthority where user.id=:userId and authority.id=:authorityId',
[userId: userId, authorityId: authorityId]
}
static UserAuthority create(User user, Authority authority, boolean flush = false) {
new UserAuthority(user: user, authority: authority).save(flush: flush, insert: true)
}
static boolean remove(User user, Authority authority, boolean flush = false) {
UserAuthority instance = UserAuthority.findByUserAndAuthority(user, authority)
if (!instance) {
return false
}
instance.delete(flush: flush)
true
}
static void removeAll(User user) {
executeUpdate 'DELETE FROM UserAuthority WHERE user=:user', [user: user]
}
static void removeAll(Authority authority) {
executeUpdate 'DELETE FROM UserAuthority WHERE authority=:authority', [authority: authority]
}
static mapping = {
id composite: ['authority', 'user']
version false
}
}
And my createUser action to create user in AdministratorActionController >>>
package administrator
import common.auth.User
class AdmistratorActionController {
def springSecurityService
def index() {
redirect(controller: 'admistratorAction', action: 'createUser')
}
def createUser = {
User user = new User(params)
def password = user.password
def salt = user.username //depends on what you're using as a salt
user.password = springSecurityService.encodePassword(password, salt)
user.save()
flash.message = "User Create Successfully !!!"
}
}
回答1:
I think you are encoding your password twice, you already have encode() on beforeInsert in the domain, I think you need not to encode it again.
回答2:
Spring Security use hash of user password + a salt. Salt is used to defeat pre-computed rainbow table attacks that could otherwise be used to greatly improve the efficiency of cracking the hashed password database. See http://en.wikipedia.org/wiki/Salt_(cryptography)
For example, if we're using username as a hash, real value in database will be:
md5(user.password + '|' + user.username)
So for two different userw with same password:
- user: username: 'user1', password: 123
- user: username: 'user2', password: 123
you'll get two different values in database:
md5('user1|123')==975204d0650cc642730866d56f66b6fbmd5('user2|123')==aa12022115555842a7f80564940ae49d
So, if hacker have access to you database he cannot guess the password.
Spring Security uses same hashing function and salt for saving and loading user. And if it unable to find user it probably means that you've used different salt for initial save and for later load from the database. Make sure that you have same salt source, and it's not changing (like id field, that have null value when you just creating a new user)
来源:https://stackoverflow.com/questions/16535832/grails-spring-security-login-is-not-working