Весна с аутентификацией JWT, получить текущего пользователя - PullRequest
0 голосов
/ 11 июня 2018

У меня есть приложение Spring Boot REST, которое использует токены JWT для авторизации.Я хочу получить текущего зарегистрированного пользователя в контроллерах, используя аннотацию @AuthenticationPrincipal.Но он всегда возвращает null, если я возвращаю пользовательскую модель из loadUserByUsername и аутентификация перестает работать.Моя модель реализует UserDetails.

Я попытался расширить org.springframework.security.core.userdetails.User, но я избавился от ошибок JWTAuthenticationFilter , что конструктор по умолчанию не существует (ApplicationUser creds = new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);)

Что не так?

UserDetailsServiceImpl.java

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        ApplicationUser applicationUser = userRepository.findByUsername(username);
        if (applicationUser == null) throw new UsernameNotFoundException(username);

        return applicationUser;
    }
}

ApplicationUser.java (модель)

@Entity
@Table(name = "users")
public class ApplicationUser implements UserDetails {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(unique = true, nullable = false)
    private String username;

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

    @Column(nullable = false)
    private String password;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

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

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

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

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

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

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
}

JWTAuthenticationFilter

public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher(LOGIN_URL));

        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException {
        try {
            ApplicationUser creds = new ObjectMapper()
                    .readValue(req.getInputStream(), ApplicationUser.class);

            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getUsername(),
                            creds.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {

        String token = Jwts.builder()
                .setSubject(((ApplicationUser) auth.getPrincipal()).getUsername())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
                .compact();

        res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
    }
}

JWTAuthorizationFilter

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authManager) {
        super(authManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                    HttpServletResponse res,
                                    FilterChain chain) throws IOException, ServletException {
        String header = req.getHeader(HEADER_STRING);

        if (header == null || !header.startsWith(TOKEN_PREFIX)) {
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            String user;
            try {
                user = Jwts.parser()
                        .setSigningKey(SECRET.getBytes())
                        .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                        .getBody()
                        .getSubject();
            } catch (SignatureException e) {
                return null;
            }

            if (user != null) return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());

            return null;
        }
        return null;
    }
}

Ответы [ 4 ]

0 голосов
/ 25 апреля 2019

Если это все еще актуально, я только что ответил на аналогичный вопрос здесь

Главное - взять authorization token из заголовка:

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
    String token = request.getHeader("Authorization").split(" ")[1];

после этогоВы можете расшифровать его и получить детали, которые вам нужны.

0 голосов
/ 12 июня 2018

Чтобы получить пользовательскую модель, я делаю следующее:

Получите модель из базы данных и установите ее в качестве принципала.

 private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            String user;
            try {
                user = Jwts.parser()
                        .setSigningKey(SECRET.getBytes())
                        .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                        .getBody()
                        .getSubject();
            } catch (SignatureException e) {
                return null;
            }

            // Get user model
            ApplicationUser userModel = userRepository.findByUsername(user);

            // Set it
            if (user != null && userModel != null) return new UsernamePasswordAuthenticationToken(userModel, null, new ArrayList<>());

            return null;
        }
        return null;
    }

Затем в контроллере получите, используя аннотацию @AuthenticationPrincipal.

public ApplicationUser getCurrentUser(@AuthenticationPrincipal ApplicationUser user) {
    return user;
}
0 голосов
/ 18 ноября 2018

В вашем случае @AuthenticationPrincipal вернет строку с именем пользователя, вы можете получить пользователя, вызвав хранилище в вашем контроллере и получив пользователя по имени пользователя или объявив хранилище как @Bean и сделав следующее.:

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

//Get the repository
private UserRepository userRepository;

public JWTAuthorizationFilter(AuthenticationManager authManager) {
    super(authManager);
}

@Override
protected void doFilterInternal(HttpServletRequest req,
                                HttpServletResponse res,
                                FilterChain chain) throws IOException, ServletException {
    String header = req.getHeader(HEADER_STRING);

    if (header == null || !header.startsWith(TOKEN_PREFIX)) {
        chain.doFilter(req, res);
        return;
    }

    UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

    SecurityContextHolder.getContext().setAuthentication(authentication);
    chain.doFilter(req, res);
}

private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
    String token = request.getHeader(HEADER_STRING);
    if (token != null) {
        // parse the token.
        String user;
        try {
            user = Jwts.parser()
                    .setSigningKey(SECRET.getBytes())
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody()
                    .getSubject();
        } catch (SignatureException e) {
            return null;
        }

        //Get your user
        UserEntity userEntity = this.userRepository.findByUsername(user);

        if (user != null) {
             //Seting in your AuthenticationPrincipal the user
             return new UsernamePasswordAuthenticationToken(userEntity, null, new ArrayList<>());
        }

        return null;
    }
    return null;
}

}
0 голосов
/ 11 июня 2018

Проверьте, используете ли вы подходящие аннотации, потому что одна из них устарела.

Документация - устарела!

Документация - отлично!


Кроме того, помните, что имя пользователя (String) следует указывать в качестве аргумента, а не типа пользователя:

Аннотация, которая используется для разрешения Authentication.getPrincipal () вАргумент метода.

Проверьте и эту тему! Это может помочь.


Я не знаю, является ли это хорошей практикой (яя еще не рассматривал 'pro' в Spring), но в моем личном проекте я получаю токен от объекта HttpServletRequest, переданного в параметре контроллера.Затем я использую класс JwtTokenUtil, у которого есть метод getUserFormToken(String token); для разрешения имени пользователя / имени пользователя.Выглядит это так:

Контроллер

@Autowired
TestService testService;

@Autowired
UserService userService;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@RequestMapping(value="/test", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public List<Test> getTestsListByUserId(HttpServletRequest req){
    String token = req.getHeader(HEADER_STRING).replace(TOKEN_PREFIX,"");
    return testService.findByUserId(userService.findByUsername(jwtTokenUtil.getUsernameFromToken(token)));
}

JwtTokenUtil

@Component
public class JwtTokenUtil implements Serializable {

public String getUsernameFromToken(String token) {
    return getClaimFromToken(token, Claims::getSubject);
}

public Date getExpirationDateFromToken(String token) {
    return getClaimFromToken(token, Claims::getExpiration);
}

public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
    final Claims claims = getAllClaimsFromToken(token);
    return claimsResolver.apply(claims);
}

private Claims getAllClaimsFromToken(String token) {
    return Jwts.parser()
            .setSigningKey(SIGNING_KEY)
            .parseClaimsJws(token)
            .getBody();
}

private Boolean isTokenExpired(String token) {
    final Date expiration = getExpirationDateFromToken(token);
    return expiration.before(new Date());
}

public String generateToken(User user) {
    return doGenerateToken(user.getUsername());
}

private String doGenerateToken(String subject) {

    Claims claims = Jwts.claims().setSubject(subject);
    claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));

    return Jwts.builder()
            .setClaims(claims)
            .setIssuer("issuer")
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
            .signWith(SignatureAlgorithm.HS256, SIGNING_KEY)
            .compact();
}

public Boolean validateToken(String token, UserDetails userDetails) {
    final String username = getUsernameFromToken(token);
    return (
           username.equals(userDetails.getUsername())
                   && !isTokenExpired(token));
    }

}

Но у меня обычно есть разные реализации фильтров в зависимости от вашей.Если вам интересно - я использовал этот учебник и реализацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...