当前位置: 首页 > 知识库问答 >
问题:

我可以在Spring Security中有多个配置来保护web应用程序和Rest API吗?

陈实
2023-03-14

我正在尝试在Spring中创建REST API和Web/MVC应用程序。它们应该使用相同的服务层。我是否可以在Spring中使用两种完全不同的配置(API的令牌身份验证,web的cookies,web的404页面,等等)?或者我应该做两个独立的Spring应用程序

共有1个答案

金秦斩
2023-03-14

Spring-MVC默认配置有助于

>

  • 控制器可以返回ModelAndView用于Web应用程序视图服务。

    Controller可以用作RestController,默认情况下响应由HttpMessageConverters处理,其中Controller方法用作Rest-API

    但是,我们可以使用spring-security,这是一个基于过滤器的框架,它在您的Rest API和使用Rest API的客户端应用程序之间充当
    安全墙(HTTP防火墙)
    或者在spring-mvc应用程序和最终用户之间充当
    安全墙(HTTP防火墙)

    • 安全web应用程序
      • 用于首次验证的登录表单。
      • 后续请求身份验证的会话。
      • 因此每个请求都将具有状态,即有状态请求
      • 每个请求都将是无状态的
      • 应首选基于令牌的身份验证
      • 如果请求来自跨源(不同源),会话将不工作

      实现-类型1。只有当身份验证令牌存在且有效时,才应访问Rest API。

      • 这种实现类型的限制是,如果web应用程序想要对Rest API进行AJAX调用,即使浏览器有有效的会话,它也不允许访问Web-API。
      • 这里Rest API仅用于无状态访问。
      • 在这里,任何第三方应用程序(跨源)都可以通过身份验证令牌访问Rest API。
      • 在这里,Rest API可以通过AJAX调用在web应用程序(同源)中访问。
      • 它具有多个http安全配置(两个http安全配置)
      • 其中@order(1)的http配置将只授权“/api/**”此配置将不考虑URL的其余部分。此http配置将配置为无状态。您应该配置OncePerRequestFilter(例如JWTAUTHFilter)的实现,并且筛选顺序可以在UsernamePasswordAuthenticationFilterBasicAuthenticationFilter之前。但您的筛选器应读取身份验证令牌的头,验证它,并创建身份验证对象,并将其设置为SecurityContext而不会失败。
      • 和@order(2)的http配置将在请求不符合一级http配置的情况下授权。并且此配置不配置JWTauthFilter,而是配置UsernamePasswordAuthenticationFilter(.formlogin()为您执行此操作)
      @Configuration
      @EnableWebSecurity
      @ComponentScan(basePackages = "com.gmail.nlpraveennl")
      public class SpringSecurityConfig
      {
          @Bean
          public PasswordEncoder passwordEncoder() 
          {
              return new BCryptPasswordEncoder();
          }
      
          @Configuration
          @Order(1)
          public static class RestApiSecurityConfig extends WebSecurityConfigurerAdapter
          {
              @Autowired
              private JwtAuthenticationTokenFilter jwtauthFilter;
      
              @Override
              protected void configure(HttpSecurity http) throws Exception
              {
                  http
                      .csrf().disable()
                      .antMatcher("/api/**")
                      .authorizeRequests()
                      .antMatchers("/api/authenticate").permitAll()
                      .antMatchers("/api/**").hasAnyRole("APIUSER")
                  .and()
                      .addFilterBefore(jwtauthFilter, UsernamePasswordAuthenticationFilter.class);
      
                  http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
              }
          }
      
          @Configuration
          @Order(2)
          public static class LoginFormSecurityConfig extends WebSecurityConfigurerAdapter
          {
              @Autowired
              private PasswordEncoder passwordEncoder;
      
              @Autowired
              public void configureInMemoryAuthentication(AuthenticationManagerBuilder auth) throws Exception
              {
                  auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("admin@123#")).roles("ADMIN");
              }
      
              @Override
              protected void configure(HttpSecurity http) throws Exception
              {
                  http
                      .csrf().disable()
                      .antMatcher("/**").authorizeRequests()
                      .antMatchers("/resources/**").permitAll()
                      .antMatchers("/**").hasRole("ADMIN")
                  .and().formLogin();
      
                  http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired=true");
              }
          }
      }
      
      • 它只有一个http安全配置
      • 其中http配置将授权所有“/**”
      • 这里为UsernamePasswordAuthenticationFilterJWTauthFilter配置了此http配置,但JWTauthFilter应在UsernamePasswordAuthenticationFilter之前配置。
      • 这里使用的技巧是,如果没有授权头,筛选器链将继续UsernamePasswordAuthenticationFilter,如果SecurityContext中没有有效的auth对象,则将调用UsernamePasswordAuthenticationFilter的尝试验证方法。如果JWTauthFilter验证令牌并将身份验证对象设置为SecurityContext,则即使筛选器链到达UsernamePasswordAuthenticationFilter尝试验证方法也不会被调用,因为SecurityContext中已经设置了身份验证对象
      @Configuration
      @EnableWebSecurity
      @ComponentScan(basePackages = "com.gmail.nlpraveennl")
      public class SpringSecurityConfig extends WebSecurityConfigurerAdapter
      {
          @Autowired
          private JwtAuthenticationTokenFilter jwtauthFilter;
      
          @Autowired
          private PasswordEncoder passwordEncoder;
      
          @Autowired
          public void configureInMemoryAuthentication(AuthenticationManagerBuilder auth) throws Exception
          {
              auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("admin@123#")).roles("ADMIN");
          }
      
          @Override
          protected void configure(HttpSecurity http) throws Exception
          {
              http
                  .csrf().disable()
                  .antMatcher("/**").authorizeRequests()
                  .antMatchers("/resources/**").permitAll()
                  .antMatchers("/api/authenticate").permitAll()
                  .antMatchers("/api/**").hasAnyRole("APIUSER","ADMIN")
                  .antMatchers("/**").hasRole("ADMIN")
              .and()
                  .formLogin()
              .and()
                  .addFilterBefore(jwtauthFilter, UsernamePasswordAuthenticationFilter.class);
      
              http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired=true");
          }
      
          @Bean
          public PasswordEncoder passwordEncoder() 
          {
              return new BCryptPasswordEncoder();
          }
      }
      

      这是关于两种类型的实现,您可以选择任何类型的实现取决于您的需求。对于这两种实现类型jwtAuthenticationTokenFilterjwtTokenUtil都是通用的,如下所示。

      @Component
      public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
      {
          @Autowired
          private JwtTokenUtil jwtTokenUtil;
      
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException
          {
              final String header = request.getHeader("Authorization");
      
              if (header != null && header.startsWith("Bearer ")) 
              {
                  String authToken = header.substring(7);
      
                  try
                  {
                      String username = jwtTokenUtil.getUsernameFromToken(authToken);
                      if (username != null)
                      {
                          if (jwtTokenUtil.validateToken(authToken, username))
                          {
                              // here username should be validated with database and get authorities from database if valid
                              // Say just to hard code
      
                              List<GrantedAuthority> authList = new ArrayList<>();
                              authList.add(new SimpleGrantedAuthority("ROLE_APIUSER"));
      
                              UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, null, authList);
                              usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
      
                              SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                          }
                          else
                          {
                              System.out.println("Token has been expired");
                              response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                              return;
                          }
                      }
                  }
                  catch (Exception e)
                  {
                      System.out.println("Unable to get JWT Token, possibly expired");
                      response.sendError(HttpServletResponse.SC_FORBIDDEN);
                      return;
                  }
              }
      
              chain.doFilter(request, response);
          }
      }
      
      @Component
      public class JwtTokenUtil implements Serializable
      {
          private static final long   serialVersionUID    = 8544329907338151549L;
      //  public static final long    JWT_TOKEN_VALIDITY  = 5 * 60 * 60 * 1000; // 5 Hours
          public static final long    JWT_TOKEN_VALIDITY  = 5 * 60 * 1000; // 5 Minutes
          private String              secret              = "my-secret";
      
          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(secret).parseClaimsJws(token).getBody();
          }
      
          private Boolean isTokenExpired(String token)
          {
              final Date expiration = getExpirationDateFromToken(token);
              return expiration.before(new Date());
          }
      
          public String generateToken(String username)
          {
              Map<String, Object> claims = new HashMap<>();
              return doGenerateToken(claims, username);
          }
      
          private String doGenerateToken(Map<String, Object> claims, String subject)
          {
              return "Bearer "+Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                      .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY)).signWith(SignatureAlgorithm.HS512, secret).compact();
          }
      
          public Boolean validateToken(String token, String usernameFromToken)
          {
              final String username = getUsernameFromToken(token);
              return (username.equals(usernameFromToken) && !isTokenExpired(token));
          }
      }
      

      您可以从下面给出的我的github存储库链接下载工作示例。
      实现类型-1
      实现类型-2

      如果你对Spring Security的执行顺序感到好奇,你可以在这里参考我的答案->Spring Security filter chain是如何工作的

  •  类似资料:
    • 如果在类路径上添加了Spring Boot Security依赖项,则Spring Boot应用程序会自动要求对所有HTTP端点进行基本身份验证。 端点“/”和“/ home”不需要任何身份验证。 所有其他端点都需要身份验证。 要将Spring Boot Security添加到Spring Boot应用程序,我们需要在构建配置文件中添加Spring Boot Starter Security依赖项

    • 问题内容: 我可以在一个应用程序中包含多个web.xml文件吗?并且两个应用程序可以具有父子关系,以便有两个web.xml吗? 问题答案: 对于Servlet 3.0以下,您不能。 如果您使用的是3.0,则有可能。 在JSR 315:Java Servlet 3.0规范中,引入了 web-fragment.xml 来实现库jar的可插入性,这些库jar打包在 WEB-INF / lib下 。web

    • 问题内容: 据我所知,我可以使用Golang运行简单的Web服务器,只需使用软件包即可,例如 其中PORT是要侦听的TCP地址。 我可以将PORT用作PORT S ,例如从一个应用程序中使用吗? 我的问题可能很愚蠢,但是“谁不问,他就不会回答!” 感谢前进! 问题答案: 你不能。 但是,您可以在不同的端口上启动多个侦听器

    • 本文向大家介绍如何使用SpringSecurity保护程序安全,包括了如何使用SpringSecurity保护程序安全的使用技巧和注意事项,需要的朋友参考一下 首先,引入依赖: 引入此依赖之后,你的web程序将拥有以下功能: 所有请求路径都需要认证 不需要特定的角色和权限 没有登录页面,使用HTTP基本身份认证 只有一个用户,名称为user 配置SpringSecurity springsecur

    • 我想为DropWizard创建几个yaml文件。其中一个包含敏感信息,另一个包含非敏感信息。 你能给我指出任何文档或例子如何在DropWizard中拥有多个配置吗?

    • 本文向大家介绍基于Spring Boot保护Web应用程序,包括了基于Spring Boot保护Web应用程序的使用技巧和注意事项,需要的朋友参考一下 如果在类路径上添加了Spring Boot Security依赖项,则Spring Boot应用程序会自动为所有HTTP端点提供基本身份验证。端点“/”和“/home”不需要任何身份验证。所有其他端点都需要身份验证。 要将Spring Boot S