简体   繁体   中英

Spring Boot Security 403 “Access Denied”

I am making a RESTFul API (not web-app) and adding Spring Security but unable to do it successfully.

After going through a lot of articles and posts here on stackoverflow , I am finally posting my question. Kindly go through it and let me know what I am missing or configuring wrongly?

Base Entity

@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
}

Role Entity

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

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

    ...getters setters
}

User Entity

@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 Class

@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/**");
    }

CurrentUserDetails

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

}

UserDetailsService

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

}

UserController Class

@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 Class

@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

Tried with both values ROLE_USER / ADMIN and USER / ADMIN

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)

I have attached all possible classes I think which are necessary. Kindly let me know what can be the issue.

Articles I went through; SO-1 , SO-2 , SO-3 , SO-4 , Article-1 , Article-2

张贴人

Resolved

@dur thanks to you for pointing it out and others as well for their helpful insights.

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.

The problem I'm seeing is that you're granting access for authority ADMIN but you're not adding this authority to the CurrentUserDetails , you're just adding their role. You should add the authority as well, ie

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

As @dur pointed out in comments, I am adding answer to my question.

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.

Since this post is very detailed, might be helpful to others as well. For corrected answer kindly refer to my git repo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM