el inicio de sesión de seguridad de Grails Spring no funciona
spring-security passwords (2)
Creo que está codificando su contraseña dos veces, ya tiene codificado () en beforeInsert en el dominio, creo que no necesita codificarlo de nuevo.
Estoy usando Grails 2.1.0. He instalado el complemento spring-security-core.
Cuando creo un usuario, lo está creando. Pero cuando intento iniciar sesión, muestra:
"Sorry, we were not able to find a user with that username and password."
Y también hay otro hecho: cuando uso la misma contraseña para diferentes usuarios, no guardo la contraseña con un valor codificado similar, como para el usuario 1. He usado la contraseña 123
que se guarda en una base de datos como esta.
d535ce213a0e8e4f9e724af47c46eea409ef401c03617b749da618a82890d743
y para el usuario 2 también utilicé la contraseña 123
y esta vez se guarda así
0849ea79a2c1bca057ded06c3053fb5bc5d7ba52b50982e73e44894d4f3e0aa6
No entiendo. ¿Puede alguien ayudarme en esto?
mi 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''
mi controlador de inicio de sesión >>>
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)
}
}
mi autoridad.groovy >>>
package common.auth
class Authority {
String authority
static mapping = {
cache true
}
static constraints = {
authority blank: false, unique: true
}
}
mi dominio de usuario.groovy donde se guardará mi usuario >>>
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)
}
}
mi 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
}
}
Y mi acción createUser para crear usuario en 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 !!!"
}
}
Spring Security usa el hash de la contraseña del usuario + una sal. La sal se usa para derrotar los ataques precomputados de la tabla del arco iris que, de lo contrario, se podrían usar para mejorar en gran medida la eficacia de descifrar la base de datos de contraseñas codificadas. Ver http://en.wikipedia.org/wiki/Salt_(cryptography)
Por ejemplo, si usamos un username
como hash, el valor real en la base de datos será:
md5(user.password + ''|'' + user.username)
Entonces para dos usuarios diferentes con la misma contraseña:
- usuario: nombre de usuario: ''usuario1'', contraseña: 123
- usuario: nombre de usuario: ''usuario2'', contraseña: 123
obtendrás dos valores diferentes en la base de datos:
-
md5(''user1|123'')
==975204d0650cc642730866d56f66b6fb
-
md5(''user2|123'')
==aa12022115555842a7f80564940ae49d
Entonces, si el hacker tiene acceso a su base de datos, no puede adivinar la contraseña.
Spring Security usa la misma función de hash y sal para guardar y cargar usuarios. Y si no puede encontrar al usuario, probablemente signifique que ha usado sal diferente para el guardado inicial y para la carga posterior de la base de datos. Asegúrate de tener la misma fuente de sal y no está cambiando (como el campo de id
, que tiene valor null
cuando creas un nuevo usuario)