具有特定表角色的spring security UserDetailsService
Posted
技术标签:
【中文标题】具有特定表角色的spring security UserDetailsService【英文标题】:spring security UserDetailsService with specific table Role 【发布时间】:2018-07-30 00:41:42 【问题描述】:我正在创建一个必须使用 Spring Security 登录 的应用。它是标准的login/logout
,我找到了很多如何创建它的教程。什么不是标准的 - 是数据库中的表角色。我不能更改数据库,我只能使用它。我为用户和角色创建了正确的实体,但我不知道如何正确编写UserDetailsServiceImpl
和loadUserByUsername
。我什至找不到接近的东西...
实体:
@Entity
@Table(name = "user")
public class User implements model.Entity
@Id
@GeneratedValue
@Column(name = "userId", nullable = false)
private int userId;
@Column(name = "firstName")
private String firstName;
@Column(name = "lastName")
private String lastName;
@Column(name = "login", nullable = false)
private String login;
@Column(name = "password", nullable = false)
private String password;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "roleId", nullable = false)
private Set<Role> roleId;
@Transient
private String confirmPassword;
public int getUserId()
return userId;
public void setUserId(int userId)
this.userId = userId;
public String getFirstName()
return firstName;
public void setFirstName(String firstName)
this.firstName = firstName;
public String getLastName()
return lastName;
public void setLastName(String lastName)
this.lastName = lastName;
public String getLogin()
return login;
public void setLogin(String login)
this.login = login;
public String getPassword()
return password;
public void setPassword(String password)
this.password = password;
public Set<Role> getRoleId()
return roleId;
public void setRoleId(Set<Role> roleId)
this.roleId = roleId;
角色:
@Entity
@Table(name = "role")
public class Role implements model.Entity
@Id
@GeneratedValue
@Column(name = "roleId", nullable = false)
private int roleId;
@Column(name = "user")
private boolean user;
@Column(name = "tutor")
private boolean tutor;
@Column(name = "admin")
private boolean admin;
public Role() // Empty constructor to have POJO class
public int getRoleId()
return roleId;
public void setRoleId(int roleId)
this.roleId = roleId;
public boolean isUser()
return user;
public void setUser(boolean user)
this.user = user;
public boolean isTutor()
return tutor;
public void setTutor(boolean tutor)
this.tutor = tutor;
public boolean isAdmin()
return admin;
public void setAdmin(boolean admin)
this.admin = admin;
@Override
public String toString()
return "Role" +
"roleId=" + roleId +
", user='" + user + '\'' +
", tutor=" + tutor + '\'' +
", admin=" + admin +
'';
所以主要问题是如何创建实现UserDetailsService的UserDetailServiceImpl的实现:
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
...
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
...
return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), grantedAuthorities);
也许我应该创建一个特殊的类,它返回用户的确切角色。或者也许还有其他方法?
我不要求为我编写代码,只是帮助我告诉我如何使它更好地实现这种角色。主要目标是划分Admin
、Tutor
和User
。
【问题讨论】:
你的实体模型坏了;它说同一用户可以有多个角色行;例如用户可以在一行中是管理员,而不能在另一行中。 @holmis83 是的,它可以是同一用户的多个角色。为什么说它坏了?或者你说的是“ManyToMany”注解? 是的,ManyToMany 意味着可能存在相互矛盾的行。 【参考方案1】:鉴于我在某种程度上同意 holmis83 的评论,因为事实上在某些情况下,role
表可能会在某些组合中包含奇怪的(或至少是重复的甚至是矛盾的)信息,您可以通过以下几种方式可以。
首先,我建议你在数据库中创建一个视图来处理role
表,这样对authorities-by-username-query
更友好
我会这样做:
SELECT roleId, 'ROLE_USER' as role FROM role WHERE user = 1
UNION
SELECT roleId, 'ROLE_TUTOR' as role FROM role WHERE tutor = 1
UNION
SELECT roleId, 'ROLE_ADMIN' as role FROM role WHERE admin = 1;
这样,对于这样的数据库模型
你会得到这样的结果:
现在,您可以使用新创建的视图而不是原始表让您的authorities-by-username-query
从用户创建inner join
。
SELECT user.login, roles_view.role FROM user as user
INNER JOIN user_has_role as user_role ON user.userId = user_role.user_userId
INNER JOIN roles_view ON user_role.role_roleId = roles_view.roleId
这将是输出:
username | role
----------------------
jlumietu | ROLE_USER
jlumietu | ROLE_ADMIN
username | ROLE_USER
username | ROLE_TUTOR
username | ROLE_ADMIN
username | ROLE_ADMIN
username | ROLE_USER
username | ROLE_TUTOR
username | ROLE_ADMIN
username | ROLE_TUTOR
由于可能有一些重复的信息,你可以使用用户名和角色来创建一个组,这样:
SELECT user.login, roles_view.role FROM user
INNER JOIN user_has_role as user_role ON user.userId = user_role.user_userId
INNER JOIN roles_view
ON user_role.role_roleId = roles_view.roleId
GROUP BY login, role;
只是为了得到这个结果:
username | role
----------------------
jlumietu | ROLE_ADMIN
jlumietu | ROLE_USER
username | ROLE_ADMIN
username | ROLE_TUTOR
username | ROLE_USER
事实上,没有必要这样做,因为 spring security 会处理它以避免重复角色,但如果手动执行查询,为了可读性,我认为这是值得的。
说了这么多,让我们检查一下安全配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<security:http use-expressions="true" authentication-manager-ref="authenticationManager">
<security:intercept-url pattern="/simple/**" access="permitAll()" />
<security:intercept-url pattern="/secured/**" access="isAuthenticated()" />
<security:form-login
login-page="/simple/login.htm"
authentication-failure-url="/simple/login.htm?error=true"
default-target-url="/secured/home.htm"
username-parameter="email"
password-parameter="password"
login-processing-url="/secured/performLogin.htm" />
<security:logout
logout-url="/secured/performLogout.htm"
logout-success-url="/simple/login.htm" />
<security:csrf disabled="true" />
</security:http>
<security:authentication-manager id="authenticationManager">
<security:authentication-provider>
<security:password-encoder hash="md5" />
<security:jdbc-user-service id="jdbcUserService" data-source-ref="dataSource"
users-by-username-query="
SELECT login AS username, password AS password, '1' AS enabled
FROM user
WHERE user.login=?"
authorities-by-username-query="
SELECT user.login, roles_view.role
FROM user
INNER JOIN user_has_role as user_role ON user.userId = user_role.user_userId
INNER JOIN roles_view ON user_role.role_roleId = roles_view.roleId
where user.login = ?
GROUP BY login, role"
/>
</security:authentication-provider>
</security:authentication-manager>
</beans:beans>
即使您无法在数据库中创建视图,您也可以通过在 authorities-by-username query
中的 role
表周围键入 select-union sql 来使其工作。
请注意,使用此解决方法,您甚至不需要编写自定义的UserDetailsService
【讨论】:
我以另一种方式做到了。我添加了 UserDetailService 检查:isAdmin isTutor isUser。如果是真的,那么 userRole:ROLE_* 现在又是一场战斗。:在 ServletContext 资源 [/WEB-INF/appconfig-data.xml] 中定义名称为 'entityManagerFactory' 的 bean 创建错误:调用 init 方法失败;嵌套异常是 javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory。如果我失败了,我会按照你的方式尝试。【参考方案2】:我做类似的事情,虽然我的用户只能拥有一个角色。
@Override
@Transactional
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException
final User user = Optional.ofNullable(findUserByEmail(username)).orElseThrow(() -> new UsernameNotFoundException("User was not found"));
List<GrantedAuthority> authorities = getUserAuthority(user.getRole());
final boolean canLogin = user.isActive() && user.isVerified();
if (!canLogin)
throw new AccountException("User is not enabled");
return buildUserForAuthentication(user, authorities);
private List<GrantedAuthority> getUserAuthority(final Role userRole)
return Collections.singletonList(new SimpleGrantedAuthority(userRole.getRole()));
private UserDetails buildUserForAuthentication(final User user, final List<GrantedAuthority> authorities)
return new org.springframework.security.core.userdetails.User(
user.getEmail(),
user.getPassword(),
true,
true,
true,
true,
authorities);
您可以将 getUserAuthority 调整为类似于:
private List<GrantedAuthority> getUserAuthorities(final Set<Role> roles)
return roles.stream().map(roleId ->
final Role role = roleRepository.findOne(roleId);
if (role.isAdmin)
return new SimpleGrantedAuthority("ROLE_ADMIN");
else if (role.isUser)
return new SimpleGrantedAuthority("ROLE_USER");
// like wise for all your roles.
).collect(Collectors.toList());
【讨论】:
以上是关于具有特定表角色的spring security UserDetailsService的主要内容,如果未能解决你的问题,请参考以下文章
具有 Active Directory 和数据库角色的 Spring Security
Spring Security 配置中的单角色多 IP 地址
Grails Spring Security 查询没有特定角色的用户