Spring Security UserDetailsS​​ervice with specific table Role

时间:2018-02-19 18:12:24

标签: spring web-applications spring-security user-roles userdetailsservice

我正在创建一个应用程序,我必须使用 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 +
                '}';
    }
}

所以主要的问题是如何创建实现UserDetailsS​​ervice的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);
    }

也许我应该创建特殊的类,它返回用户的确切角色..或者可能还有其他方法?

我不要求为我编码,只是帮我说如何让它更好地实现这样的角色。主要目标是划分AdminTutorUser

2 个答案:

答案 0 :(得分:0)

我做了类似的事情,尽管我的用户只能有一个角色。

@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());
}

答案 1 :(得分:0)

鉴于我在某种程度上同意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;

就这样,对于像这样的数据库模型

enter image description here

你会得到这样的结果:

enter image description here

现在,您可以使用新创建的视图而不是原始表格使用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安全会处理它以避免重复的角色,但为了便于阅读,如果查询是手动执行的,我认为这是值得的。

说完这一切之后,让我们检查一下安全配置:

<?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中的authorities-by-username query表中键入select-union sql,您就可以设置它。

请注意,通过此解决方法,您甚至无需编写自定义的UserDetailsService