Hybrid authentication - Spring MVC session based + JWT token based

前端 未结 1 1897
醉梦人生
醉梦人生 2021-01-21 13:32

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

相关标签:
1条回答
  • 2021-01-21 14:34

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

    Yes you can. By having two authentication processing filters.

    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).

    Recommended URL pattern
    api-url-pattern    = "/api/**"
    webApp-url-pattern = "/**"
    
    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.

    • 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.

    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.

    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)

    Spring security configuration
    @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 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 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));
        }
    }
    
    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

    0 讨论(0)
提交回复
热议问题