spring 具有特定 table 角色的安全 UserDetailsService
spring security UserDetailsService with specific table Role
我正在创建一个必须使用 Spring 安全登录 的应用程序。它是标准的 login/logout
,我找到了很多如何创建它的教程。不标准的是数据库中的 table 角色。我无法更改数据库,只能使用它。我为用户和角色创建了正确的实体,但我无法理解如何正确编写 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);
}
也许我应该创建特殊的 class,其中 return 用户的确切角色。或者也许还有其他方法?
我不要求为我编写代码,只是帮我说说如何更好地实现这种角色。主要目标是划分Admin
、Tutor
和User
.
我做了类似的事情,虽然我的用户只能有一个角色。
@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());
}
考虑到我以某种方式同意 holmis83 的评论,因为实际上在某些情况下 role
table 可能在某些组合中有奇怪的(或者至少,重复甚至矛盾的)信息, 您可以采用多种方法。
首先,我建议您在数据库中创建一个视图来处理 role
table,这样会更 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
使用新创建的视图而不是原始 table 从用户创建 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 安全会处理它以避免重复角色,但为了可读性,如果手动执行查询,我认为这是非常值得的。
说了这么多,让我们检查一下安全配置:
<?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>
即使您无法在数据库中创建视图,您也可以设法让它工作,只需在 role
table 周围键入 select-union sql authorities-by-username query
.
请注意,使用此解决方法,您甚至不需要编写自定义 UserDetailsService
我正在创建一个必须使用 Spring 安全登录 的应用程序。它是标准的 login/logout
,我找到了很多如何创建它的教程。不标准的是数据库中的 table 角色。我无法更改数据库,只能使用它。我为用户和角色创建了正确的实体,但我无法理解如何正确编写 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);
}
也许我应该创建特殊的 class,其中 return 用户的确切角色。或者也许还有其他方法?
我不要求为我编写代码,只是帮我说说如何更好地实现这种角色。主要目标是划分Admin
、Tutor
和User
.
我做了类似的事情,虽然我的用户只能有一个角色。
@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());
}
考虑到我以某种方式同意 holmis83 的评论,因为实际上在某些情况下 role
table 可能在某些组合中有奇怪的(或者至少,重复甚至矛盾的)信息, 您可以采用多种方法。
首先,我建议您在数据库中创建一个视图来处理 role
table,这样会更 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
使用新创建的视图而不是原始 table 从用户创建 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 安全会处理它以避免重复角色,但为了可读性,如果手动执行查询,我认为这是非常值得的。
说了这么多,让我们检查一下安全配置:
<?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>
即使您无法在数据库中创建视图,您也可以设法让它工作,只需在 role
table 周围键入 select-union sql authorities-by-username query
.
请注意,使用此解决方法,您甚至不需要编写自定义 UserDetailsService