使用Spring和Spring安全性访问拒绝并允许功能

当前,我正在尝试使用带有弹簧安全性的spring MVC和spring boot实现认证示例。在我的示例应用程序中,我想做的是-我在一个URL的标题中发送一个身份验证令牌。我需要从URL中获取此身份验证令牌并进行解码。如果用户名和密码匹配,则只需将控件转移到端点“ api / getStudent / v1”或类似的地方。否则,从那里只需要给出拒绝的响应即可。

为此,我目前尝试使用Spring Security的身份验证提供程序。但是它不适合从请求的标头中获取令牌。在这里,我的困惑是,从spring security出发,我必须在这里实现哪种方法?谁能建议一种标准的实施方式?或任何有关此类实现的文档?

happysxin 回答:使用Spring和Spring安全性访问拒绝并允许功能

您需要做的就是创建一个自定义安全过滤器,并在spring security BasicAuthenticationFilter之前插入此过滤器。示例代码-

public class CustomAuthenticationFilter extends OncePerRequestFilter {

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

        String authHeader = request.getHeaders("Authorization");
        //Decode the authHeader

        //Validate the authHeader with your username & password
        if(invalid) {
            //throw exception and abort processing
        }
        filterChain.doFilter(request,response);
    }
}

现在,您可以创建bean或将其命名为@component,以便spring可以为您选择并创建bean。

在您的安全配置中,添加以下内容-

@Configuration
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.addFilterAfter(new CustomAuthenticationFilter(),BasicAuthenticationFilter.class);
    }
}
,

好像您正在使用REST API一样,您可以使用类似于(https://medium.com/@hantsy/protect-rest-apis-with-spring-security-and-jwt-5fbc90305cc5)的JWT和自定义过滤器

,
  

我在一个URL的标题中发送一个身份验证令牌。我需要   从URL获取此身份验证令牌并进行解码。如果用户名和   密码匹配...

通常,使用令牌进行身份验证的目的是摆脱用户名和密码检查。

Spring Security支持的开箱即用的基本HTTP身份验证假定在HTTP标头中传递base64编码的用户名和密码: Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l(base64编码的Aladdin:OpenSesame)。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
          .antMatchers("/public").permitAll()
          .anyRequest().authenticated()
          .and()
          .httpBasic();
    }
}

如果仍然需要以其他方式从令牌中提取用户名和密码,请考虑以下示例。

考虑到您具有以下REST控制器:

@RestController
public class TestRestController {

    @GetMapping("/api/getStudent/v1")
    public String helloWorld() {
        return "Hello,World!";
    }

    @GetMapping("/info")
    public String test() {
        return "Test";
    }
}

为了使端点/api/getStudent/v1受保护和/info公开,并从HTTP请求标头中提取主体和凭证,您需要实现自定义AbstractAuthenticationProcessingFilter

public class HeaderUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public HeaderUsernamePasswordAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
        super(requiresAuthenticationRequestMatcher);
        setAuthenticationSuccessHandler((request,response,authentication) -> {
        });
        setAuthenticationFailureHandler((request,exception) ->
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED,exception.getMessage()));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException,IOException,ServletException {
        String token = request.getHeader("token");
        String username = token; //get username from token
        String password = token; //get password from token
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(username,password);
        return getAuthenticationManager().authenticate(authenticationToken);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain,Authentication authResult) throws IOException,ServletException {
        super.successfulAuthentication(request,chain,authResult);
        chain.doFilter(request,response);
    }
}

此过滤器必须从传入的标头中提取令牌的主体和凭证,然后尝试通过Spring Security进行认证。

接下来,您必须创建此自定义过滤器的实例,并将Spring Security配置为在安全过滤器链(.addFilterBefore(authenticationFilter(),UsernamePasswordAuthenticationFilter.class))中添加该过滤器:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public HeaderUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
        HeaderUsernamePasswordAuthenticationFilter authenticationFilter =
                new HeaderUsernamePasswordAuthenticationFilter(new AntPathRequestMatcher("/api/**"));
        authenticationFilter.setAuthenticationManager(authenticationManagerBean());
        return authenticationFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable()
                .addFilterBefore(
                        authenticationFilter(),UsernamePasswordAuthenticationFilter.class);
    }

    //...
}

让过滤器了解Spring Security authenticationManagerBeanauthenticationFilter.setAuthenticationManager(authenticationManagerBean());很重要。

您可以通过传递RequestMatcher来配置要保护的端点,例如: new AntPathRequestMatcher("/api/**")

对于测试,您可以创建内存UserDetailsService,并使用用户名test,密码test和权限admin测试用户:

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //...

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("test")
            .password(passwordEncoder().encode("test"))
            .authorities("admin");
    }
}

运行该应用程序,然后尝试在不进行身份验证的情况下访问公共端点:

curl -i http://localhost:8080/info
HTTP/1.1 200 
Test

未经身份验证的受保护端点:

curl -i http://localhost:8080/api/getStudent/v1 
HTTP/1.1 401

没有无效令牌的受保护端点:

curl -i http://localhost:8080/api/getStudent/v1 -H 'token: not_valid'
HTTP/1.1 401

最后是具有有效令牌的受保护端点:

curl -i http://localhost:8080/api/getStudent/v1 -H 'token: test'
HTTP/1.1 200 
Hello,World!
,

您可以尝试以下方法。我在这里使用了JWT身份验证。根据您的问题,您可以使用spring的@Preauthorize注释对端点“ api / getStudent / v1”进行预授权。 以下是将引导用户登录的终点。

@PostMapping("/signin")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginForm loginRequest) {

    Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(loginRequest.getEmail(),loginRequest.getPassword()));

    SecurityContextHolder.getContext().setAuthentication(authentication);

    String jwt = jwtProvider.generateJwtToken(authentication);

    UserPrinciple userPrinciple = (UserPrinciple) authentication.getPrincipal();
    String name = userRepo.findById(userPrinciple.getId()).get().getName();

    return ResponseEntity.ok(new JwtResponse(jwt,userPrinciple.getUsername(),userPrinciple.getAuthorities(),name,userPrinciple.getGender()));
}

以下是 WebSecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        prePostEnabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtAuthEntryPoint unauthorizedHandler;

    @Bean
    public JwtAuthTokenFilter authenticationJwtTokenFilter() {
        return new JwtAuthTokenFilter();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

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

    @Bean
    public AuthorizationRequestRepository<OAuth2AuthorizationRequest> customAuthorizationRequestRepository() {
        return new HttpSessionOAuth2AuthorizationRequestRepository();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors().and()
                 .csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);



        http.addFilterBefore(authenticationJwtTokenFilter(),UsernamePasswordAuthenticationFilter.class);
    }


}

JWTProvider 类之后,包括生成JWT令牌的方法。(注意:我已将每个用户的电子邮件设置为用户名。您可以根据自己的意愿进行操作)

@Component
public class JwtProvider {

    @Autowired
    UserRepository userRepo;

    private static final Logger logger = LoggerFactory.getLogger(JwtProvider.class);

    public String generateJwtToken(Authentication authentication) {

        UserPrinciple userPrincipal = (UserPrinciple) authentication.getPrincipal();
        String name = userRepo.findById(userPrincipal.getId()).get().getName();

        return Jwts.builder()
                        .setSubject((userPrincipal.getUsername())) //getUsername returns the email
                        .claim("id",userPrincipal.getId() )
                        .claim("name",name)
                        .setIssuedAt(new Date())
                        .setExpiration(new Date((new Date()).getTime() + EXPIRATION_TIME))
                        .signWith(SignatureAlgorithm.HS512,SECRET)
                        .compact();
    }

    public String generateJwtToken(UserPrinciple userPrincipal) {

        String name = userRepo.findById(userPrincipal.getId()).get().getName();

        return Jwts.builder()
                .setSubject((userPrincipal.getUsername())) //getUsername returns the email
                .claim("id",userPrincipal.getId() )
                .claim("name",name)
                .setIssuedAt(new Date())
                .setExpiration(new Date((new Date()).getTime() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512,SECRET)
                .compact();
    }

    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(SECRET).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error("Invalid JWT signature -> Message: {} ",e);
        } catch (MalformedJwtException e) {
            logger.error("Invalid JWT token -> Message: {}",e);
        } catch (ExpiredJwtException e) {
            logger.error("Expired JWT token -> Message: {}",e);
        } catch (UnsupportedJwtException e) {
            logger.error("Unsupported JWT token -> Message: {}",e);
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims string is empty -> Message: {}",e);
        }

        return false;
    }

    public String getUserNameFromJwtToken(String token) {
        return Jwts.parser()
                            .setSigningKey(SECRET)
                            .parseClaimsJws(token)
                            .getBody().getSubject();
    }
}

以下是在WebSecurityConfig类中启动的 JWTAuthTokenFilter 类。它在这里从rquest解码令牌并检查令牌是否有效

public class JwtAuthTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtProvider tokenProvider;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request,FilterChain filterChain)
            throws ServletException,IOException {
        try {

            String jwt = getJwt(request);
            if (jwt != null && tokenProvider.validateJwtToken(jwt)) {
                String email = tokenProvider.getUserNameFromJwtToken(jwt);//returns the email instead of username

                UserDetails userDetails = userDetailsService.loadUserByUsername(email);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails,null,userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            logger.error("Can NOT set user authentication -> Message: {}",e);
        }

        filterChain.doFilter(request,response);
    }

    private String getJwt(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.replace("Bearer ","");
        }

        return null;
    }
}

以下是 JWTAuthEntryPoint 。检查 WebSecurityConfig 类以了解此类的使用

@Component
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request,AuthenticationException e) 
                                 throws IOException,ServletException {

        logger.error("Unauthorized error. Message - {}",e.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Error -> Unauthorized");
    }
}

以下是我为约束创建的类

public class SecurityConstraints {
    public static final String SECRET = "********";//add any secret you want
    public static final long EXPIRATION_TIME = 864_000_000L;
}
本文链接:https://www.f2er.com/3166045.html

大家都在问