我正在使用Spring Security插件编写Grails应用程序。
我已将GORM生成的查询启用到控制台中,我注意到每个请求Security都会向数据库查询用户,并通过用户名选择它们。
我的意图是通过它的ID来加载用户,而不是用户名来提高性能。
我知道有可能覆盖UserDetailsService
方法loadUserByUsername(String username)
,但此方法既用于在会话期间刷新用户的凭据,也用于登录表单,实际上我想要验证用户的用户名。
我有三个问题:
GrailsUser
(UserDetails
的实施)而不是常规用户名中注入用户ID而不是用户名并使用long selectById = Long.valueOf(String username)
吗?答案 0 :(得分:2)
最后我设法解决了这个问题。查询由以下内容生成:
springSecurityService.getCurrentUser()
不幸的是,此方法通过用户名(来自Principal对象)获取User模型类并将其映射到数据库字段,最多配置为:
grails.plugin.springsecurity.userLookup.usernamePropertyName
如上所述in documentation。
我试过了
grails.plugin.springsecurity.userLookup.usernamePropertyName = 'id'
但是我收到了从String到Long的类转换异常。
解决方法很简单 - 创建自己的原则,将username
字段输入为Long。
请参阅我的解决方案中的PrincipalProxy
:
package com.selly.util.security
import java.security.Principal;
import grails.plugin.springsecurity.userdetails.GrailsUser
import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority
class AppMetadataAuthenticationToken implements Authentication, Principal {
private boolean authenticated
private GrailsUser userDetails
private Principal principal
public AppMetadataAuthenticationToken(GrailsUser userDetails) {
this.userDetails = userDetails
this.principal = new PrincipalProxy(userDetails)
}
public GrailsUser getUser() {
return userDetails
}
public String getUsername() {
return userDetails.getUsername()
}
@Override
public String getName() {
return userDetails.getUsername()
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return userDetails.getAuthorities()
}
@Override
public Object getCredentials() {
return userDetails.password
}
@Override
public Object getDetails() {
return getUser()
}
@Override
public Object getPrincipal() {
return principal
}
@Override
public boolean isAuthenticated() {
return authenticated
}
@Override
public void setAuthenticated(boolean authenticated) throws IllegalArgumentException {
this.authenticated = authenticated
}
static class PrincipalProxy implements Principal {
GrailsUser grailsUser
Long username
public PrincipalProxy(GrailsUser grailsUser) {
this.grailsUser = grailsUser
this.username = grailsUser.id
}
@Override
public String getName() {
return grailsUser.id
}
}
}
要返回此令牌,只需注册您自己的AuthenticationProvider
:
package com.selly.util.security;
import grails.plugin.springsecurity.SpringSecurityService
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
public class AppUsernamePasswordAuthenticationProvider extends DaoAuthenticationProvider implements AuthenticationProvider {
SpringSecurityService springSecurityService
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
def token = (UsernamePasswordAuthenticationToken) authentication
def user = userDetailsService.loadUserByUsername(authentication.principal)
if(!user)
throw new UsernameNotFoundException("Cannot find user", authentication.principal)
if(!passwordEncoder.isPasswordValid(user.password, authentication.credentials, null))
throw new BadCredentialsException("Invalid password")
return new AppMetadataAuthenticationToken(user)
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
package com.selly.util.security;
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
public class AppMetadataAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// TODO Auto-generated method stub
return authentication;
}
@Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
return AppMetadataAuthenticationToken.class.isAssignableFrom(authentication);
}
}
在resources.groovy
appUsernamePasswordAuthenticationProvider(AppUsernamePasswordAuthenticationProvider) {
userDetailsService = ref('userDetailsService')
passwordEncoder = ref('passwordEncoder')
userCache = ref('userCache')
saltSource = ref('saltSource')
preAuthenticationChecks = ref('preAuthenticationChecks')
postAuthenticationChecks = ref('postAuthenticationChecks')
springSecurityService = ref('springSecurityService')
}
在Config.groovy
:
grails.plugin.springsecurity.providerNames = [
'appMetadataAuthenticationProvider',
'appUsernamePasswordAuthenticationProvider',
// 'daoAuthenticationProvider',
// 'anonymousAuthenticationProvider',
// 'rememberMeAuthenticationProvider'
]
现在一切都很完美:
Hibernate: select this_.id as id13_0_, this_.account_expired as account2_13_0_, this_.account_locked as account3_13_0_, this_.enabled as enabled13_0_, this_."password" as password5_13_0_, this_.password_expired as password6_13_0_, this_.username as username13_0_, this_.workspace as workspace13_0_ from users this_ where (**this_.id=?**) limit ?
除了使用getCurrentUser()
之外,您还可以使用getPrincipal()
投射到之前填充的对象,其数据多于Principal
界面提供的数据。