Home > OS >  Spring boot. userPrincipal is null
Spring boot. userPrincipal is null

Time:09-05

I'm new to java spring and I'm trying to practise my skills through developing my own app. And now I'm trying to create authorization using jwt tokens. I wrote the loginc of it and got struggled with the small problem. I don't know why, but my principal user that I'm trying to authenticate is always null.

So here's the logic:

  1. First of all, here's all entities I created:

UserEntity

@Entity
@Table(name = "users")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String email;
    private String login;
    private String password;
    private String name;
    private String surname;
    @ManyToOne
    @JoinColumn(name="schools_id")
    private SchoolEntity school;
    @ManyToOne
    @JoinColumn(name="classes_id")
    private ClassEntity userClass;

    @ManyToMany(fetch = FetchType.EAGER)
    private List<RoleEntity> roles = new ArrayList<>();

    public UserEntity() {
    }

    public List<RoleEntity> getRoles() {
        return roles;
    }

    public void setRoles(List<RoleEntity> roles) {
        this.roles = roles;
    }

    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public long getId() {
        return id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public SchoolEntity getSchool() {
        return school;
    }

    public void setSchool(SchoolEntity school) {
        this.school = school;
    }

    public ClassEntity getUserClass() {
        return userClass;
    }

    public void setUserClass(ClassEntity userClass) {
        this.userClass = userClass;
    }
}

RoleEntity

@Entity
@Table(name = "roles")
public class RoleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;

    public RoleEntity() {
    }

    public long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

SchoolEntity

@Entity
@Table(name = "schools")
public class SchoolEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
    @OneToMany(mappedBy = "school")
    private List<UserEntity> user;

    public SchoolEntity() {
    }

    public long getId() {
        return id;
    }

    public List<UserEntity> getUser() {
        return user;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

ClassEntity

@Entity
@Table(name = "classes")
public class ClassEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private int number;
    private String letter;
    @OneToMany(mappedBy = "userClass")
    private List<UserEntity> students;

    public ClassEntity() {
    }

    public long getId() {
        return id;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public String getLetter() {
        return letter;
    }

    public void setLetter(String letter) {
        this.letter = letter;
    }

    public List<UserEntity> getStudents() {
        return students;
    }

    public void setStudents(List<UserEntity> students) {
        this.students = students;
    }
}
  1. Then I added implementation of UserDetailsService to my UserService class, where I overrided loadUserByUsername, which finds user by login field:
Service
public class UserService implements UserDetailsService {

    @Autowired
    private UserRepository userRepo;

    @Autowired
    private PasswordEncoder passwordEncoder;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserEntity user = userRepo.findByLogin(username);
        if(user == null) {
            throw new UsernameNotFoundException("user with such username doesn't exists");
        }
        ArrayList<SimpleGrantedAuthority> userRoles = new ArrayList<>();
        for(var role: user.getRoles()) {
            userRoles.add(new SimpleGrantedAuthority(role.getName()));
        }
        User u =  new User(user.getLogin(), user.getPassword(), userRoles);
        return u;
    }

And how it shown on the screenshot, the UserDetails object was created:

enter image description here

  1. After this, I created the Authentication filter to make jwt tokens and authenticate users:
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final AuthenticationManager authenticationManager;

    public AuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(username, password);
        return authenticationManager.authenticate(usernamePasswordAuthenticationToken);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                            FilterChain chain, Authentication authentication) throws IOException, ServletException {
        UserDetails userPrincipal = (User) request.getUserPrincipal();
        Algorithm algorithm = JWTConfig.getAlgorithm();
        String accessToken = JWT.create()
                .withSubject(userPrincipal.getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis()   30 * 1000 * 60))
                .withClaim("roles", userPrincipal.getAuthorities().stream()
                        .map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
                .sign(algorithm);
        String refreshToken = JWT.create()
                .withSubject(userPrincipal.getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis()   60 * 1000 * 60))
                .sign(algorithm);
        Map<String, String> tokens = new HashMap<>();
        tokens.put("access_token", accessToken);
        tokens.put("refresh_token", refreshToken);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(response.getOutputStream(), tokens);
    }
}
  1. I also created the Authorization filter. Here it is:
public class AuthorizationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        if(header != null && header.startsWith("Bearer ")) {
            try {
                String token = header.substring("Bearer ".length());
                JWTVerifier jwtVerifier = JWT.require(JWTConfig.getAlgorithm()).build();
                DecodedJWT decodedJWT = jwtVerifier.verify(token);
                String login = decodedJWT.getSubject();
                List<SimpleGrantedAuthority> roles = new ArrayList<>();
                for (var role : decodedJWT.getClaim("roles").asArray(String.class)) {
                    roles.add(new SimpleGrantedAuthority(role));
                }
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                        new UsernamePasswordAuthenticationToken(login, null, roles);
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                filterChain.doFilter(request, response);
            } catch (Exception e) {
                response.setHeader("error", e.getMessage());
                response.sendError(403);
            }
        }
        else {
            filterChain.doFilter(request, response);
        }
    }
}
  1. After all of it, I set up the SecurityConfig:
@Configuration @EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        AuthenticationFilter customAuthenticationFilter = new AuthenticationFilter(authenticationManagerBean());
        customAuthenticationFilter.setFilterProcessesUrl("/api/login");
        http.csrf().disable();
        http.authorizeRequests().antMatchers("/api/login/**").permitAll();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.authorizeRequests().antMatchers(HttpMethod.POST, "/api/users/").permitAll();
        http.addFilter(customAuthenticationFilter);
        http.addFilterBefore(new AuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

But when I start the programm, it tells me that the user principal I get in AuthenticationFilter is null. This is the line where exception was thrown:

UserDetails userPrincipal = (User) request.getUserPrincipal();

So if you know, what can be the problem, please tell me. I'd really apreciate it!

CodePudding user response:

After successful authentication Spring Security sets Authentication object to a SecurityContextHolder, so you can get your username from Authentication, injected in successfulAuthentication() method - just change your code to this:

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        Algorithm algorithm = JWTConfig.getAlgorithm();
        String accessToken = JWT.create()
                .withSubject(authentication.getName()) // <- getting name here
                .withExpiresAt(new Date(System.currentTimeMillis()   30 * 1000 * 60))
                .withClaim("roles", authentication.getAuthorities().stream() // <- getting authorities here
                        .map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
                .sign(algorithm);
        String refreshToken = JWT.create()
                .withSubject(authentication.getName()) // <- and name again here
                .withExpiresAt(new Date(System.currentTimeMillis()   60 * 1000 * 60))
                .sign(algorithm);
        // other code
    }

The reason you get null there is that Spring only sets Principal object to a HttpServletRequest in SecurityContextHolderAwareRequestFilter using SecurityContextHolderAwareRequestWrapper, and that filter is invoked after UsernamePasswordAuthenticationFilter.
So in your custom UsernamePasswordAuthenticationFilter implementation Spring has not set Principal object to the HttpServletRequest yet.

Spring Security filter chain order can be found here in the official reference documentation.

  • Related