I'm trying to enable spring security in a spring boot rest services project and I'm getting some problems.
I configured it with this code
@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private LdapAuthenticationProvider ldapAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic().and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthenticationProvider);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
and implemented a custom authentication provider in order to login to LDAP (which has a non standard configuration so I wasn't able to make the default ldap provider works)
@Component
public class LdapAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String email = authentication.getName();
String password = authentication.getCredentials().toString();
LdapConnection ldap = new LdapConnection();
String uid = ldap.getUserUID(email);
if(uid == null || uid == ""){
throw new BadCredentialsException("User " + email + " not found");
}
if(ldap.login(uid, password)){
return new UsernamePasswordAuthenticationToken(uid, null, new ArrayList<>());
}else{
throw new BadCredentialsException("Bad credentials");
}
}
@Override
public boolean supports(Class<?> authentication) {
return true;
//To indicate that this authenticationprovider can handle the auth request. since there's currently only one way of logging in, always return true
}
}
This code is working fine, in the sense that calling my services with a basic authorization header it is able to correctly login and return the service called. The problems started when I tried to insert a different authorization/authentication. Instead of using the basic authentication I would like to pass the credential from a form in my react front end, so I would like to pass them as a json body in a POST call. (the idea is then to generate a jwt token and use that for the following communication).
So I changed the configure method to this:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
and defined a custom authentication filter:
public class JWTAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
@Autowired
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException{
String requestBody;
try{
requestBody = IOUtils.toString(req.getReader());
JsonParser jsonParser = JsonParserFactory.getJsonParser();
Map<String, Object> requestMap = jsonParser.parseMap(requestBody);
return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(requestMap.get("email"), requestMap.get("password"), new ArrayList<>()));
}catch(IOException e){
throw new InternalAuthenticationServiceException("Something goes wrong parsing the request body",e );
}
}
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException{
JwtTokenProvider tokenProvider = new JwtTokenProvider();
String token = tokenProvider.generateToken(auth.getPrincipal().toString());
Cookie cookie = new Cookie("jwt",token);
cookie.setHttpOnly(true);
cookie.setSecure(true);
res.addCookie(cookie);
}
}
Problem is, whatever I'm doing, the runtime doesn't seems to enter in this filter at all. What am I missing? I guess is something big and stupid but I can't figure it out...
UPDATE: the problem seems to be that the UsernamePassWordAuthenticationFilter can be called only through a form. I then change my code to extend AbstractAuthenticationProcessingFilter instead.
The modified filter:
public class JWTAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super("/api/secureLogin");
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException{
String requestBody;
try{
requestBody = IOUtils.toString(req.getReader());
JsonParser jsonParser = JsonParserFactory.getJsonParser();
Map<String, Object> requestMap = jsonParser.parseMap(requestBody);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(requestMap.get("email"), requestMap.get("password"), new ArrayList<>());
return authenticationManager.authenticate(token);
}catch(IOException e){
throw new InternalAuthenticationServiceException("Something goes wrong parsing the request body",e );
}
}
}
and the modified configure method:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/secureLogin").permitAll()
.antMatchers(HttpMethod.GET, "/api").permitAll()
.antMatchers("/api/**").authenticated()
.and()
.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
You expect the filter to be triggered by accessing the path api/secureLogin
. By default UsernamePasswordAuthenticationFilter
is triggered only by accessing /login
.
If you add following line in the constructor of the JWTAuthenticationFilter
which extends UsernamePasswordAuthenticationFilter
it should work:
this.setFilterProcessesUrl("/api/secureLogin");
Hi @Mikyjpeg
You can use UsernamePassWordAuthenticationFilter . It does not have to be called from a form as you mentioned.
As long as it is called via a POST
method with the url /login
(instead of your api/secureLogin
url), it will be executed.
The constructor uses a request matcher that only allows this url & request method:
public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); }
I see your adding the configuration while overriding the configure method, try adding the filter mapping in your web.xml. Something like this under the 'web-app' node:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>JWTAuthenticationFilter</filter-name>
<filter-class>com.yourProject.JWTAuthenticationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>JWTAuthenticationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Answer by ADAM has worked for me. Those who are working with annotation based configuration can add following
@Override protected Filter[] getServletFilters() {
return new Filter[]{
new JwtAuthenticationFilter()
};
}
Into your AppInitialiser extends AbstractAnnotationConfigDispatcherServletInitializer
public class BeforeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public BeforeAuthenticationFilter() {
super(new AntPathRequestMatcher("api/secureLogin", "POST"));
super.setFilterProcessesUrl("api/secureLogin");
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.