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
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).
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.
How to configureNote: 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.
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 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