【问题标题】:Spring Boot Security 403 "Access Denied"Spring Boot 安全 403“拒绝访问”
【发布时间】:2019-09-23 21:04:35
【问题描述】:

我正在制作一个 RESTFul API(不是 Web 应用)并添加 Spring Security 但无法成功。

stackoverflow 上浏览了很多文章和帖子后,我终于发布了我的问题。请仔细检查一下,让我知道我缺少什么或配置错误?

基础实体

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
abstract class BaseEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "ID", nullable = false, updatable = false)
    private Long ID;

    @CreatedBy
    @Column(name = "CreatedBy", nullable = false, updatable = false)
    private String createdBy;

    @CreatedDate
    @Column(name = "CreatedDate", nullable = false, updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedBy
    @Column(name = "ModifiedBy")
    private String modifiedBy;

    @LastModifiedDate
    @Column(name = "ModifiedDate")
    private LocalDateTime modifiedDate;

    ...getters setters
}

角色实体

@Entity
@Table(name = "ROLE")
public class Role extends BaseEntity {

    @Column(name = "Name")
    private String name;

    ...getters setters
}

用户实体

@Entity
@Table(name = "USER")
public class User extends BaseEntity {

    @Column(name = "EmiratesID", unique = true, nullable = false, updatable = false)
    private String emiratesID;

    @Column(name = "FirstName")
    private String firstName;

    @Column(name = "LastName")
    private String lastName;

    @Column(name = "StaffID", unique = true, nullable = false, updatable = false)
    private String staffID;

    @Column(name = "Email", unique = true, nullable = false)
    private String email;

    @Column(name = "Password", nullable = false)
    private String password;

    @ManyToOne(optional = false, cascade = CascadeType.MERGE)
    @JoinColumn(name = "ROLE_ID")
    private Role role;

    ...getters setters

    public UserDetails currentUserDetails() {
        return CurrentUserDetails.create(this);
    }

}

SecurtiyConfig 类

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final DataSource dataSource;
    private final UserDetailsServiceImplementation userDetailsService;

    @Autowired
    public SecurityConfig(final DataSource dataSource, final UserDetailsServiceImplementation userDetailsService) {
        this.dataSource = dataSource;
        this.userDetailsService = userDetailsService;

    }

    @Bean
    BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests()
                .antMatchers("/console/**").permitAll()
                .antMatchers("/", "/greetUser", "/register", "/login").permitAll()
                .antMatchers("/user/**").hasAnyAuthority(ROLES.USER.getValue(), ROLES.ADMIN.getValue())
                .antMatchers("/admin/**").hasAuthority(ROLES.ADMIN.getValue()).anyRequest().authenticated();
        httpSecurity.csrf().disable();

        // required to make H2 console work with Spring Security
        httpSecurity.headers().frameOptions().disable();

    }

    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

    }

    @Override
    public void configure(WebSecurity webSecurity) {

        webSecurity.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
    }

当前用户详细信息

public class CurrentUserDetails implements UserDetails {

    private String ROLE_PREFIX = "ROLE_";

    private Long userID;
    private String emiratesID;
    private String firstName;
    private String lastName;
    private String staffID;
    private String email;
    private String password;
    private Role role;

    public CurrentUserDetails(Long ID, String emiratesID, String firstName,
                              String lastName, String staffID, String email,
                              String password, Role role) {

        super();
        this.userID = ID;
        this.emiratesID = emiratesID;
        this.firstName = firstName;
        this.lastName = lastName;
        this.staffID = staffID;
        this.email = email;
        this.password = password;
        this.role = role;

    }

    public Long getUserID() {
        return userID;
    }

    public String getEmiratesID() {
        return emiratesID;
    }

    public String getEmail() {
        return this.email;
    }

    public Role getRole() {
        return this.role;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> grantedAuthority = new ArrayList<>();

        grantedAuthority.add(new SimpleGrantedAuthority(ROLE_PREFIX + role.getName()));

        return grantedAuthority;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.email;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    /**
     * Helper method to add all details of Current User into Security User Object
     * @param user User
     * @return UserDetails
     */
    public static UserDetails create(User user) {
        return new CurrentUserDetails(user.getID(), user.getEmiratesID(),
                                      user.getFirstName(), user.getLastName(),
                                      user.getStaffID(), user.getEmail(),
                                      user.getPassword(), user.getRole());
    }

}

UserDetailsS​​ervice

@Component/@Service
public class UserDetailsServiceImplementation implements UserDetailsService {

    private static final Logger userDetailsServiceImplementationLogger = LogManager.getLogger(UserDetailsServiceImplementation.class);
    private final UserRepository userRepository;

    @Autowired
    public UserDetailsServiceImplementation(final UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (StringUtils.isEmpty(username)) {
            userDetailsServiceImplementationLogger.error("UserDetailsServiceImplementation.loadUserByUsername() :: FAILED");

            throw new UsernameNotFoundException("UserName is not passed");
        }

        User userFound = userRepository.findByEmail(username);

        if (userFound == null) {
            userDetailsServiceImplementationLogger.error("No user found with given username = {}", username);

            throw new UsernameNotFoundException("No user found with given username");
        }

        return userFound.currentUserDetails();
    }

}

用户控制器类

@RestController
@RequestMapping(value = "/user")
public class UserController {

    private static Logger userControllerLogger = LogManager.getLogger(UserController.class);

    @Autowired
    private PropertiesConfig propertiesConfig;

    @Autowired
    private UserManager userManager;

    @RequestMapping(value = "/listAll", method = RequestMethod.GET)
    public ResponseEntity<Map<String, Object>> getUsersList() {
        userControllerLogger.info("UserController.getUsersList()[/listAll] :: method call ---- STARTS");

        LinkedHashMap<String, Object> result = userManager.findAllUsers();

        userControllerLogger.info("UserController.getUsersList()[/listAll] :: method call ---- ENDS");

        return new ResponseEntity<>(result, HttpStatus.OK);
    }

}

AdminContrller 类

@RestController
@RequestMapping(value = "/admin")
public class AdminController {

    private static final Logger adminControllerLogger = LogManager.getLogger(AdminController.class);

    private final PropertiesConfig propertiesConfig;
    private final UserManager userManager;

    @Autowired
    public AdminController(final PropertiesConfig propertiesConfig, final UserManager userManager) {
        this.propertiesConfig = propertiesConfig;
        this.userManager = userManager;

    }

    @RequestMapping(value = "/home", method = {RequestMethod.GET})
    public ResponseEntity<String> adminPortal(@RequestBody String adminName) {
        adminControllerLogger.info("AdminController.adminPortal()[/home] :: method call ---- STARTS");

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        UserDTO adminUser = userManager.findUserByEmail(auth.getName());

        if (adminUser == null) {
            throw new UsernameNotFoundException(propertiesConfig.getProperty(ApplicationProperties.Messages.NO_USER_FOUND.getValue()));
        }

        adminControllerLogger.info("AdminController.adminPortal()[/home] :: method call ---- ENDS");

        return new ResponseEntity<>(ApplicationConstants.GeneralConstants.WELCOME.getValue() + adminUser.getStaffID(), HttpStatus.OK);

    }

}

data.sql

尝试使用 ROLE_USER/ADMINUSER 这两个值/管理员

INSERT INTO ROLE(ID, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, Name) VALUES (-100, 'Muhammad Faisal Hyder', now(), '', null, 'ROLE_ADMIN'/'ADMIN')
INSERT INTO ROLE(ID, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, Name) VALUES (-101, 'Muhammad Faisal Hyder', now(), '', null, 'ROLE_USER'/'USER')

INSERT INTO USER(ID, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, EmiratesID, FirstName, LastName, Email, StaffID, Password, ROLE_ID) VALUES (-1, 'Muhammad Faisal Hyder', now(), '', null, 'ABCDEF12345', 'Muhammad Faisal', 'Hyder', 'faisal.hyder@gmail.com', 'S776781', '$2a$10$qr.SAgYewyCOh6gFGutaWOQcCYMFqSSpbVZo.oqsc428xpwoliu7C', -100)
INSERT INTO USER(ID, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, EmiratesID, FirstName, LastName, Email, StaffID, Password, ROLE_ID) VALUES (-2, 'Muhammad Faisal Hyder', now(), '', null, 'BCDEFG12345', 'John', 'Smith', 'John.Smith@gmail.com', 'S776741', '$2a$10$j9IjidIgwDfNGjNi8UhxAeLuoO8qgr/UH9W9.LmWJd/ohynhI7UJO', -101)

我已经附上了我认为必要的所有可能的课程。请让我知道可能是什么问题。

我浏览过的文章; SO-1SO-2SO-3SO-4Article-1Article-2

已解决

@dur 感谢您的指出以及其他人的有益见解。

1- Use ROLE_ in db entries.
2- Once prefix is added in db then no need to explicitly add this in
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities(){...}
3- .and().httpBasic(); was missing from SpringSecurity configuration.
4- This is very detailed, might be helpful to others as well.

【问题讨论】:

  • 你能试试把.antMatchers("/admin/**").hasAuthority(ROLES.ADMIN.getValue()).anyRequest().authenticated();换成.antMatchers("/admin/**").hasAuthority("ADMIN").anyRequest().authenticated();吗?
  • @AvijitBarua 好友,已经尝试过了。还添加了两个 ADMIN。在grantAuthorities 中的ROLE_ADMIN,同时覆盖它。在 DB ADMIN 和 ROLE_ADMIN 中尝试了两种方式。没有任何效果。我在考虑安全描述,我需要告诉安全用户名期望哪个字段名称。顺便说一句,当我们使用 hasAuthority 而不是 hasRole 时,Spring 会自己添加 ROLE_ 前缀。
  • 在 UserDetails 中覆盖用户名时,我正在返回电子邮件,因此用户名应该是电子邮件...
  • @dur 1st- 我尝试在 sql 中同时使用 ADMIN/ROLE_ADMIN。第二 - 我在 GrantedAuthority 中使用了 ROLE_ 和没有。第三 - 我根据创建的用户附上了 req/res 的图片,而不是 SQL 中已经删除的图片。第 4 个问题可能是我没有声明 httpBasic()。我现在解决了。 5th-hasAuthority 需要 ROLE_ADMIN/USER 因为 spring 会自动将它添加到 GrantedAuthority 中。

标签: spring rest spring-boot spring-security postman


【解决方案1】:

我看到的问题是您授予权限ADMIN 的访问权限,但您没有将此权限添加到CurrentUserDetails,您只是添加了他们的角色。您还应该添加权限,即

@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> grantedAuthority = new ArrayList<>();

        grantedAuthority.add(new SimpleGrantedAuthority(ROLE_PREFIX + role.getName()));

        // add authority in addition to role (no role prefix)
        grantedAuthority.add(new SimpleGrantedAuthority(role.getName()));

        return grantedAuthority;
    }

【讨论】:

  • 感谢@msparer 的回答,我也进行了搜索,发现当我们使用 .hasAuthority 时,我们不需要检查 Prefix 它会自动添加。因此,为了授予权限,我在覆盖它时明确添加了它。
  • 还是一样,403。我用的是H2,已经在data.sql中添加了几个语句。条目非常好,即使用户已正确注册。但是使用该用户/管理员来查询相应的资源,它会尖叫 403:/ 我认为一个潜在的问题是我没有告诉 SecurityConfig 期望传递哪个字段并使用“用户名”,但如果我这样做,那么它将是基于表单的登录,当我这样做时,它不支持 GET 方法......
  • 在 UserDetails 中覆盖用户名时,我正在返回电子邮件,因此用户名应该是电子邮件...
【解决方案2】:

正如@dur 在 cmets 中指出的那样,我正在为我的问题添加答案。

1- Use ROLE_ in db entries.
2- Once prefix is added in db then no need to explicitly add this in
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities(){...}
3- .and().httpBasic(); was missing from SpringSecurity configuration.

由于这篇文章非常详细,可能对其他人也有帮助。更正答案请参考我的git repo

【讨论】:

    猜你喜欢
    • 2020-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-05
    • 2020-01-25
    • 2012-07-16
    • 2015-09-07
    相关资源
    最近更新 更多