混合身份验证-基于Spring MVC会话+基于JWT令牌 [英] Hybrid authentication - Spring MVC session based + JWT token based

查看:87
本文介绍了混合身份验证-基于Spring MVC会话+基于JWT令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一种情况,我正在使用Spring MVC(jsp,控制器,服务,dao)和基于会话的身份验证.但是现在我很少将这些URL用作用于集成目的的RESTful Web服务.

I have a situation, I am using Spring MVC (jsp, controllers, service, dao) and session based authentication. But now few urls I am using as a RESTful Web service for integration purpose.

仅对于那些请求,我需要使用基于令牌(例如JWT)的身份验证.

For those requests only, I need to use token (for eg JWT) based authentication.

因此,是否有可能在同一项目中同时使用两种身份验证.

So, is there any possibility that I can use both type of authentication within same project.

推荐答案

我有可能在同一项目中同时使用两种身份验证.

is there any possibility that I can use both type of authentication within same project.

是的,可以.通过具有两个身份验证处理过滤器.

Yes you can. By having two authentication processing filters.

过滤器-1 :用于Rest API(JwtAuthTokenFilter),该API应该是无状态的,并通过每次请求中发送的授权令牌进行标识.
过滤器-2 :您需要另一个过滤器(UsernamePasswordAuthenticationFilter)默认情况下,如果通过http.formLogin()配置它,spring-security将提供此过滤器.在这里,每个请求都由关联的会话(JSESSIONID cookie)标识.如果请求不包含有效的会话,则它将被重定向到身份验证入口点(例如:登录页面).

Filter - 1: for Rest API (JwtAuthTokenFilter) which should be stateless and identified by Authorization token sent in request each time.
Filter - 2: You need another filter (UsernamePasswordAuthenticationFilter) By default spring-security provides this if you configure it by http.formLogin(). Here each request is identified by the session(JSESSIONID cookie) associated. If request does not contain valid session then it will be redirected to authentication-entry-point (say: login-page).

api-url-pattern    = "/api/**"
webApp-url-pattern = "/**"

工作方式

    具有/api/**
  • URL将通过JwtAuthTokenFilter传递,在那里它将读取令牌,并且如果它具有有效的令牌,则设置身份验证对象并继续进行链接.如果没有有效的请求,则链会断开,并且将以401(未授权)状态发送响应.

    How it works

    • URL's with /api/** will be passed through JwtAuthTokenFilter where it will read the token and if it has valid token, sets authentication object and chain continues. If it does not have the valid request then chain gets broken and response will be sent with 401(Unauthorized) status.

      /api/**以外的URL将由UsernamePasswordAuthenticationFilter处理[这是由.formLogin()配置配置的spring security中的默认设置]它将检查有效的会话,如果它不包含有效的会话,它将进行检查.重定向到配置的logoutSuccessUrl.

      URL's other than /api/** will be handled by UsernamePasswordAuthenticationFilter [which is default in spring security configured by .formLogin() configuration] It will check for valid session, if it does not contain the valid session it will redirects to logoutSuccessUrl configured.

      注意: 您的Webapp无法使用现有会话访问API.您可以使用Jwt令牌从Web应用程序访问API.

      Note: Your Webapp can not access APIs by using existing session. What option you have is to use Jwt token to access API from Web application.

      如何配置

      要实现两个不同的身份验证处理过滤器, 您应该以不同的顺序配置多个http安全性配置
      可以通过在安全配置类中声明静态类来配置多个http安全配置,如下所示.
      (即使OP要求在概念上明智地展示代码,也可能会有所帮助.)

      How to configure

      To achieve two different authentication processing filter, You should configure multiple http security configuration with different order
      Multiple http security configuration can be configured by declaring static classes in your security configuration class as given below.
      (Even though OP asked concept wise presenting it code wise. It may help you for reference)

      @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("/customlogin?expired=true");
              }
          }
      }
      

      Jwt身份验证令牌过滤器

      Jwt authentication token filter

      @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);
                  System.out.println(authToken);
      
                  try
                  {
                      String username = jwtTokenUtil.getUsernameFromToken(authToken);
                      if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
                      {
                          if (jwtTokenUtil.validateToken(authToken, username))
                          {
                              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);
                          }
                      }
                  }
                  catch (Exception e)
                  {
                      System.out.println("Unable to get JWT Token, possibly expired");
                  }
              }
      
              chain.doFilter(request, response);
          }
      }
      

      Jwt令牌实用程序类

      Jwt token util class

      @Component
      public class JwtTokenUtil implements Serializable
      {
          private static final long   serialVersionUID    = 8544329907338151549L;
          public static final long    JWT_TOKEN_VALIDITY  = 5 * 60 * 60;
          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 * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact();
          }
      
          public Boolean validateToken(String token, String usernameFromToken)
          {
              final String username = getUsernameFromToken(token);
              return (username.equals(usernameFromToken) && !isTokenExpired(token));
          }
      }
      

      调度程序Servlet配置

      Dispatcher Servlet Configuration

      @Configuration
      @EnableWebMvc
      @ComponentScan(basePackages = "com.gmail.nlpraveennl") //Do not skip componentscan
      public class ServletConfiguration implements WebMvcConfigurer
      {
           @Bean
           public ViewResolver configureViewResolver() 
           {
               InternalResourceViewResolver viewResolve = new InternalResourceViewResolver();
               viewResolve.setPrefix("/WEB-INF/jsp/");
               viewResolve.setSuffix(".jsp");
      
               return viewResolve;
           }
      
          @Bean
          public ResourceBundleMessageSource messageSource()
          {
              ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
              messageSource.setBasename("messages");
              messageSource.setDefaultEncoding("UTF-8");
              messageSource.setUseCodeAsDefaultMessage(true);
              return messageSource;
          }
      
          @Override
          public void addResourceHandlers(ResourceHandlerRegistry registry)
          {
              registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
          }
      }
      

      以上解释是一种实现,我在

      Above explanation is one type of implementation, i have explained other type of implementation(where Rest APIs can be accessed by auth token as well as session) in my another answer which you can refer here

      这篇关于混合身份验证-基于Spring MVC会话+基于JWT令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆