spring-security Spring Boot ,userPrincipal为空

c3frrgcw  于 2022-11-11  发布在  Spring
关注(0)|答案(1)|浏览(502)

我是java spring的新手,我正在尝试通过开发自己的应用程序来练习我的技能。现在我正在尝试使用jwt令牌创建授权。我写了它的loginc,并遇到了一个小问题。我不知道为什么,但我试图验证的主要用户总是空。
逻辑是这样的:
1.首先,这是我创建的所有实体:
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.然后,我将UserDetailsService的实现添加到我的UserService类中,在这里我覆盖了loadUserByUsername,它通过 login 字段查找用户:

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

在屏幕截图中,UserDetails对象是如何创建的:

1.在此之后,我创建了身份验证过滤器来生成jwt令牌并对用户进行身份验证:

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.我还创建了授权过滤器。

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.完成所有这些之后,我设置了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();
    }
}

但是当我启动程序时,它告诉我在AuthenticationFilter中得到的用户主体是null。这是抛出异常的行:

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

所以如果你知道,有什么问题,请告诉我。我真的很感激!

pxy2qtax

pxy2qtax1#

成功验证后,Spring Security将Authentication对象设置为SecurityContextHolder,这样你就可以从Authentication中获取你的username,注入到successfulAuthentication()方法中--只需将你的代码更改为:

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

您在那里得到null的原因是Spring只使用SecurityContextHolderAwareRequestWrapperPrincipal对象设置为SecurityContextHolderAwareRequestFilter中的HttpServletRequest,并且该过滤器是在**UsernamePasswordAuthenticationFilter之后**调用的。
因此,在您的自定义UsernamePasswordAuthenticationFilter实现中,Spring还没有将Principal对象设置为HttpServletRequest
Spring Security过滤器链的顺序可以在here in the official reference documentation中找到。

相关问题