简体   繁体   中英

Spring Security with JWT Roles not working

I am using Spring Boot and Java 14.

I am trying to get role based authentication working. I am using JWT. I can get authentication working, ie I get the JWT and use that token for all future requests successfully.

However, I cannot get the roles based authority working, ie I always get a 403 if the Security config tries to match an endpoint to a role.

For exmple, if I send the following request with a valid jwt where the user does have the correct role ( 'approver admin' ):

GET http://localhost:8080/approvals

Header:  Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJyaWNoYXJkbWFyYWlzIiwiZXhwIjoxNTkyODgwNzUzLCJpYXQiOjE1OTI4NDQ3NTN9.MgiH5--a4U8phKz_jmjmBuxRt8iqhhcHHnxrhxGOQqM

I get the following reponse:

{
    "timestamp": "2020-06-22T16:56:22.113+00:00",
    "status": 403,
    "error": "Forbidden",
    "message": "Forbidden",
    "path": "/approvals"
}

My code:

SecurityConfig.java

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("datasource1")
    private DataSource dataSource;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //auth.userDetailsService(approvalUserDetailsService);
        auth.jdbcAuthentication().dataSource(dataSource)
        .usersByUsernameQuery("SELECT username, password, (NOT disabled) as enabled FROM members "+
                "WHERE username = ?")
        .authoritiesByUsernameQuery("SELECT m.username, t.name as authority FROM members m " +
                "JOIN administrator a ON a.member_id = m.member_id " +
                "JOIN admin_type t ON t.admin_type_id = a.admin_type_id "+
                "WHERE m.username = ?");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // set up the jwt auth
        http.cors().disable();
        http.csrf().disable().authorizeRequests().antMatchers("/authenticate").permitAll()//.anyRequest().authenticated()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .and().exceptionHandling()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // don't manage sessions, using jwt
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

        // define the role mappings
        http.authorizeRequests()
                .antMatchers("/admin").hasRole("approver admin")
                .antMatchers("/approvals").hasRole("approver admin")
                .antMatchers("/hello").permitAll();
    }

JwtRequestFilter.java

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private ApprovalUserDetailsService approvalUserDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");
        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtTokenUtil.extractUserName(jwt);
        }
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.approvalUserDetailsService.loadUserByUsername(username);
            if (jwtTokenUtil.validteToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

ApprovalUserDetailsService.java

@Service
public class ApprovalUserDetailsService implements UserDetailsService {

    @Autowired
    NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        String sql = "SELECT username, password FROM members WHERE username = :userName";
//        String sql = "SELECT m.username, m.password, t.name as authority FROM members m " +
//                "JOIN administrator a ON a.member_id = m.member_id " +
//                "JOIN admin_type t ON t.admin_type_id = a.admin_type_id " +
//                "WHERE m.username = :userName";
        SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("userName", userName);
        User users = namedParameterJdbcTemplate.queryForObject(sql, namedParameters, new UserDetailsRowMapper());
        return users;
        //        return new User("foo", "foo", new ArrayList<>());
    }

    class UserDetailsRowMapper implements RowMapper<User> {
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            List<? extends GrantedAuthority> roles = new ArrayList<>();
            //String authority = rs.getString("authority");
            User user = new User(rs.getString("username"), rs.getString("password"), roles);
            return user;
        }
    }
}

In the ApprovalUserDetailsService , the List<? extends GrantedAuthority> roles = new ArrayList<>(); List<? extends GrantedAuthority> roles = new ArrayList<>(); is just an empty list. Does this need to be populated with the 'approver admin' role? Is that my problem?

The reason I have left it empty, because the tutorial I followed left it empty. I think the filter may only be used for getting the jwt, and not for the role validation, so it's not needed. Or am I incorrect and it needs to be populated?

If thought that the query in the SecurityConfig.java is were it gets the roles to validate. ie

    .authoritiesByUsernameQuery("SELECT m.username, t.name as authority FROM members m " +
            "JOIN administrator a ON a.member_id = m.member_id " +
            "JOIN admin_type t ON t.admin_type_id = a.admin_type_id "+
            "WHERE m.username = ?");

在此处输入图像描述

In the ApprovalUserDetailsService, the List<? extends GrantedAuthority> roles = new ArrayList<>(); is just an empty list. Does this need to be populated with the 'approver admin' role? Is that my problem?

Yes. It needs to be populated with the 'approver admin'. You can use AuthorityUtils to convert a string role name to the GrantedAuthority :

    class UserDetailsRowMapper implements RowMapper<User> {
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            String authority = rs.getString("authority");
            User user = new User(rs.getString("username"), rs.getString("password"), AuthorityUtils.createAuthorityList(authority););
            return user;
        }
    }

Also hasRole("approver admin") expects the user has the role ROLE_approver admin which the ROLE_ prefix will be added automatically. Change to use hasAuthority() for the exact match without adding any ROLE_ prefix:

  http.authorizeRequests()
        .antMatchers("/admin").hasAuthority("approver admin")
        .antMatchers("/approvals").hasAuthority("approver admin")

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