Проверка подлинности токена на предъявителя Spring Boot дает 401 - PullRequest
0 голосов
/ 13 октября 2018

Я новичок в Spring boot, поэтому, пожалуйста, помогите мне.Я получил его до такой степени, что я могу сгенерировать Bearer Token с неаутентифицированным запросом.Затем я хочу использовать этот токен для использования с конечной точкой, чтобы мой запрос был аутентифицирован - вот где моя проблема. Я всегда получаю 401, похоже, что-то не так с моей конфигурацией.Вот мой код

public class ApplicationUser {

private String username;
private String password;
private String role;

public ApplicationUser(String username, String password, String role) {
    this.username = username;
    this.password = password;
    this.role = role;
}

public String getUsername() {
    return username;
}

public String getPassword() {
    return password;
}

public String getRole() {
    return role;
    }
}

JwtConfig Класс:

@Component("jwtConfig")
public class JwtConfig {
@Value("${security.jwt.uri:/auth/**}")
private String Uri;

@Value("${security.jwt.header:Authorization}")
private String header;

@Value("${security.jwt.prefix:Bearer }")
private String prefix;

@Value("${security.jwt.expiration:#{24*60*60}}")
private int expiration;

@Value("${security.jwt.secret:JwtSecretKey}")
private String secret;

public String getUri() {
    return Uri;
}

public String getHeader() {
    return header;
}

public String getPrefix() {
    return prefix;
}

public int getExpiration() {
    return expiration;
}

public String getSecret() {
    return secret;
}
}

JwtTokenAuthenticationFilter

public class JwtTokenAuthenticationFilter extends  OncePerRequestFilter {

private final JwtConfig jwtConfig;

public JwtTokenAuthenticationFilter(JwtConfig jwtConfig) {
    this.jwtConfig = jwtConfig;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws ServletException, IOException {

    // 1. get the authentication header. Tokens are supposed to be passed in the authentication header
    String header = request.getHeader(jwtConfig.getHeader());

    // 2. validate the header and check the prefix
    if(header == null || !header.startsWith(jwtConfig.getPrefix())) {
        chain.doFilter(request, response);          // If not valid, go to the next filter.
        return;
    }

    // If there is no token provided and hence the user won't be authenticated.
    // It's Ok. Maybe the user accessing a public path or asking for a token.

    // All secured paths that needs a token are already defined and secured in config class.
    // And If user tried to access without access token, then he won't be authenticated and an exception will be thrown.

    // 3. Get the token
    String token = header.replace(jwtConfig.getPrefix(), "");

    try {   // exceptions might be thrown in creating the claims if for example the token is expired

        // 4. Validate the token
        Claims claims = Jwts.parser()
                .setSigningKey(jwtConfig.getSecret().getBytes())
                .parseClaimsJws(token)
                .getBody();

        String username = claims.getSubject();
        if(username != null) {
            @SuppressWarnings("unchecked")
            List<String> authorities = (List<String>) claims.get("authorities");

            // 5. Create auth object
            // UsernamePasswordAuthenticationToken: A built-in object, used by spring to represent the current authenticated / being authenticated user.
            // It needs a list of authorities, which has type of GrantedAuthority interface, where SimpleGrantedAuthority is an implementation of that interface
            UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                    username, null, authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));

            // 6. Authenticate the user
            // Now, user is authenticated
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

    } catch (Exception e) {
        // In case of failure. Make sure it's clear; so guarantee user won't be authenticated
        SecurityContextHolder.clearContext();
    }

    // go to the next filter in the filter chain
    chain.doFilter(request, response);
}

}

JwtUsernameAndPasswordAuthenticationFilter

public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter   {

// We use auth manager to validate the user credentials
private AuthenticationManager authManager;

private final JwtConfig jwtConfig;

public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authManager, JwtConfig jwtConfig) {
    this.authManager = authManager;
    this.jwtConfig = jwtConfig;

    // By default, UsernamePasswordAuthenticationFilter listens to "/login" path.
    // In our case, we use "/auth". So, we need to override the defaults.
    this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(jwtConfig.getUri(), "POST"));
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException {

    try {

        // 1. Get credentials from request
        UserCredentials creds = new ObjectMapper().readValue(request.getInputStream(), UserCredentials.class);

        // 2. Create auth object (contains credentials) which will be used by auth manager
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                creds.getUsername(), creds.getPassword(), Collections.emptyList());

        // 3. Authentication manager authenticate the user, and use UserDetialsServiceImpl::loadUserByUsername() method to load the user.
        return authManager.authenticate(authToken);

    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

// Upon successful authentication, generate a token.
// The 'auth' passed to successfulAuthentication() is the current authenticated user.
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                        Authentication auth) throws IOException, ServletException {

    Long now = System.currentTimeMillis();
    String token = Jwts.builder()
            .setSubject(auth.getName())
            // Convert to list of strings.
            // This is important because it affects the way we get them back in the Gateway.
            .claim("authorities", auth.getAuthorities().stream()
                    .map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
            .setIssuedAt(new Date(now))
            .setExpiration(new Date(now + jwtConfig.getExpiration() * 1000))  // in milliseconds
            .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret().getBytes())
            .compact();

    // Add token to header
    response.addHeader(jwtConfig.getHeader(), jwtConfig.getPrefix() + token);
}

// A (temporary) class just to represent the user credentials
private static class UserCredentials {
    private String username, password;

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

Конфиденциальность1015 *

UserDetailsServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private BCryptPasswordEncoder encoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    final List<ApplicationUser> users = Arrays.asList(
            new ApplicationUser("omar",encoder.encode("12345"), "USER"),
            new ApplicationUser("admin", encoder.encode("12345"), "ADMIN")
    );

    for(ApplicationUser appUser: users) {
        if(appUser.getUsername().equals(username)) {
            List<GrantedAuthority> grantedAuthorities = AuthorityUtils
                    .commaSeparatedStringToAuthorityList( appUser.getRole());

            return new User(appUser.getUsername(), appUser.getPassword(), grantedAuthorities);
        }
    }

    // If user not found. Throw this exception.
    throw new UsernameNotFoundException("Username: " + username + " not found");

}
}

WebSecurity

@EnableWebSecurity(debug=true)
@Order(1000)
public class WebSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private JwtConfig jwtConfig;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf().disable()
            // make sure we use stateless session; session won't be used to store user's state.
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            // handle an authorized attempts
            .exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
            .and()
            // Add a filter to validate the tokens with every request
            .addFilterAfter(new JwtTokenAuthenticationFilter(jwtConfig), UsernamePasswordAuthenticationFilter.class)
            // authorization requests config
            .authorizeRequests()
            // allow all who are accessing "auth" service
            .antMatchers(HttpMethod.POST, jwtConfig.getUri()).permitAll()
            // must be an admin if trying to access admin area (authentication is also required here)
            .antMatchers("/v1/cooks").access("hasRole('ADMIN')")
            //for other uris
         //   .antMatchers(HttpMethod.GET, "/v1/**").hasRole("USER")
            // Any other request must be authenticated
            .anyRequest().authenticated();
}

}

Контроллер

@RestController


public class CookController {

@Autowired
private CookService cookService;
// Get All Cooks
@GetMapping("/v1/cooks")
public List<Cook> getAllCooks(){
    return cookService.getAllCooks();
}

application.properties

zuul.routes.auth-service.path=/auth/**
zuul.routes.auth-service.service-id=AUTH-SERVICE
zuul.routes.auth-service.strip-prefix=false
zuul.routes.auth-service.sensitive-headers=Cookie,Set-Cookie  

spring.application.name=auth-service
server.port=9100
eureka.client.service-url.default-zone=http://localhost:8761/eureka

вызовv1/cooks всегда возвращает 401. Какая часть конфигурации мне не хватает?Я следовал за документацией в https://medium.com/omarelgabrys-blog/microservices-with-spring-boot-authentication-with-jwt-part-3-fafc9d7187e8

, но теперь я полностью потерян.URL запроса: GET http://localhost:9100/v1/cooks

Ответ:

{ "timestamp": "2018-10-13T20:08:13.804+0000", "status": 401, "error": "Unauthorized", "message": "No message available", "path": "/v1/cooks" }

pom.xml

 <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.restdocs</groupId>
        <artifactId>spring-restdocs-mockmvc</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.16</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.0-b170201.1204</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

РЕДАКТИРОВАТЬ: добавлены application.properties и зависимости отП

Ответы [ 2 ]

0 голосов
/ 14 октября 2018

Что касается ваших требований, вам действительно не нужно несколько конфигураций безопасности http, пока вы не используете несколько аутентификаций для нескольких путей (например, для какого-то пути вы хотели бы иметь JWT, а для некоторых вы хотели бы иметь базовую аутентификацию или аутентификацию 2).).

Так что удалите SecurityCredentialsConfig и обновите WebSecurity до уровня ниже, и все будет хорошо.

@Configuration
@EnableWebSecurity(debug = true)    // Enable security config. This annotation denotes config for spring security.
public class WebSecurity extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtConfig jwtConfig;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                // make sure we use stateless session; session won't be used to store user's state.
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // authorization requests config
                .authorizeRequests()
                // allow all who are accessing "auth" service
                .antMatchers(HttpMethod.POST, jwtConfig.getUri()).permitAll()
                // must be an admin if trying to access admin area (authentication is also required here)
                .antMatchers("/v1/cooks/**").hasAuthority("ADMIN")
                //for other uris
                //   .antMatchers(HttpMethod.GET, "/v1/**").hasRole("USER")
                // Any other request must be authenticated
                .anyRequest().authenticated()
                .and()
                // handle an authorized attempts
                .exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and()
                // Add a filter to validate the tokens with every request
                .addFilterAfter(new JwtTokenAuthenticationFilter(jwtConfig), UsernamePasswordAuthenticationFilter.class)
                .addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig));
    }

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

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
0 голосов
/ 14 октября 2018

Попробуйте добавить это

/ **

онлайн в классе WebSecurity,

.antMatchers("/v1/cooks/**" ).access("hasRole('ADMIN')")

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

...