RESTful API authentication using Spring Boot and LDAP - api

I have a requirement to build a RESTful API to authenticate users via LDAP(External LDAP) on Angular front end. I have tried various option but it doesn't seem to work. I guess it could be related to the way LDAP credentials are set.
Here is the LDAP config in application.properties.
ldap.enabled=true
####### LDAP ##############
ldap.urls=ldap://test.ldap.com:389/ <br>
ldap.base.dn=dc=comp,dc=company,dc=com<br>
ldap.username=cn=q123123,ou=health,ou=internal<br>
ldap.password=johnwick123<br>
ldap.user.dn.pattern=uid={0}<br>
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOG =
LoggerFactory.getLogger(WebSecurityConfig.class);
#Value("${ldap.urls}")
private String ldapUrls;
#Value("${ldap.base.dn}")
private String ldapBaseDn;
#Value("${ldap.username}")
private String ldapSecurityPrincipal;
#Value("${ldap.password}")
private String ldapPrincipalPassword;
#Value("${ldap.user.dn.pattern}")
private String ldapUserDnPattern;
#Value("${ldap.enabled}")
private String ldapEnabled;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
try {
auth.ldapAuthentication().contextSource().url(ldapUrls + ldapBaseDn).managerDn(ldapSecurityPrincipal)
.managerPassword(ldapPrincipalPassword).and().userDnPatterns(ldapUserDnPattern);
} catch (Exception ex) {
LOG.error("Handle exception here");
throw ex;
}
}
#Bean
public static PropertySourcesPlaceholderConfigurer
PropertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Input to Spring Boot API is { "username":, "password": }
Output from Spring Boot API is { "status": 200, "message": "user authenticated"}

Related

Spring-security/Grails app - Custom WebSecurity configurations not getting called

My project is based on Grail 2.5.6 and Spring plugins. I'm trying to create a custom auth provider, filter and token extending their respective basic classes.
this.getAuthenticationManager().authenticate(authRequest)
In my filter the authentication manager is always null. So, it throws cannot invoke authenticate() on a null object. When I debug on the authenticationManager, it lists other provider names but my custom one.
Here is my custom web security config
#Configuration
#EnableGlobalMethodSecurity(securedEnabled=true)
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
OrbisAuthenticationProvider orbisAuthenticationProvider
public CustomWebSecurityConfig() {
super()
log.debug "configure custom security"
print("configure custom security")
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
print("configure method 1")
log.debug "configure method 1"
auth.authenticationProvider(orbisAuthenticationProvider)
}
#Bean(name= BeanIds.AUTHENTICATION_MANAGER)
#Override
AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean()
}
#Bean
OrbisAuthenticationFilter orbisAuthenticationProvider() throws Exception {
log.debug "orbis Authentication provider"
OrbisAuthenticationProvider orbisAuthenticationProvider = new OrbisAuthenticationProvider(authenticationManagerBean())
return orbisAuthenticationProvider
}
#Bean
#Autowired
public OrbisAuthenticationFilter orbisAuthenticationFilter() throws Exception {
print("configure orbis filtr")
OrbisAuthenticationFilter oaf = new OrbisAuthenticationFilter()
oaf.setAuthenticationManager(authenticationManagerBean())
oaf.setFilterProcessesUrl("j_orbis_security_check")
oaf.setUsernameParameter("email")
oaf.setPasswordParameter("password")
oaf.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler()
.setDefaultTargetUrl("/oauth/authorize"))
oaf.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler()
.setDefaultFailureUrl("/loginWithOrbis"))
oaf.afterPropertiesSet()
return oaf
}
}
On debugging, it doesn't look like any of these methods are getting called. The annotations don't seem enough to get picked up. I had tried #ComponentScan too.
Do I have to inject this security config somewhere? How do I get authManager to be available in my filter?
OrbisAuthFilter
class OrbisAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// #Autowired
OrbisAuthenticationProvider orbisAuthenticationProvider
OrbisAuthenticationFilter() {
super("/j_orbis_security_check")
orbisAuthenticationProvider = new OrbisAuthenticationProvider()
}
void afterPropertiesSet() {
assert authenticationManager != null, 'authenticationManager must be specified'
}
#Override
Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("email")
String password = request.getParameter("password")
String accessCode = request.getParameter("accessCode")
OrbisAuthenticationToken authRequestForAuthentication = new OrbisAuthenticationToken(username, password, accessCode)
// This throws error because getAuthenticationManager returns null
// authRequestForAuthentication = this.getAuthenticationManager.authenticate(authRequestForAuthentication)
//This works if I instantiate the orbis provider object in the constructor
authRequestForAuthentication = this.orbisAuthenticationProvider.authenticate(authRequestForAuthentication)
SecurityContextHolder.getContext().setAuthentication(authRequestForAuthentication)
return authRequestForAuthentication
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
OrbisAuthProvider
class OrbisAuthenticationProvider implements AuthenticationProvider {
#Override
Authentication authenticate(Authentication authentication) throws AuthenticationException {
OrbisAuthenticationToken orbisAuth = (OrbisAuthenticationToken) authentication
String username = orbisAuth.principal
String password = orbisAuth.credentials
String orbisAccessCode = orbisAuth.orbisAccessCode
def urlToUse = 'https://coopstatus.neu.edu/sail_api/full.aspx?' + 'ac=' + orbisAccessCode + '&e='+ username + '&p=' + password
HttpClient httpClient = DefaultHttpClient.newInstance()
HttpGet getRequest = new HttpGet(urlToUse)
HttpResponse httpResponse = httpClient.execute(getRequest)
JSONObject orbisResponse = new JSONObject(httpResponse.getEntity().getContent().getText())
// if(orbisResponse.get("IsFound")) {
// //Return error not authenticated
// }
Collection<GrantedAuthority> orbisUserGrantedAuthorities = getLDAPUserAuthorities(orbisResponse.get("Email"))
orbisAuth = new OrbisAuthenticationToken(username, password, orbisAccessCode, orbisUserGrantedAuthorities)
return orbisAuth
}
private Collection<GrantedAuthority> getLDAPUserAuthorities(String username) {
LDAPUserDetails currentLdapUserDetails
try {
currentLdapUserDetails = new LDAPUserDetailsService().loadUserByOrbisUsername(username)
log.debug currentLdapUserDetails
} catch(org.springframework.security.core.userdetails.UsernameNotFoundException e) {
log.error("User " + username + " not found in ldap", e)
}
Collection<GrantedAuthority> authorities = new ArrayList<>()
for (String authority : currentLdapUserDetails.authorities) {
authorities.add(new SimpleGrantedAuthority(authority))
}
return authorities
}
#Override
public boolean supports(Class<?> authentication) {
return (OrbisAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
Resources.groovy
import edu.neu.security.OrbisAuthenticationFilter
import edu.neu.security.OrbisAuthenticationProvider
beans = {
userDetailsService(edu.neu.security.LDAPUserDetailsService)
orbisAuthenticationProvider(OrbisAuthenticationProvider)
orbisAuthenticationFilter(OrbisAuthenticationFilter) {
orbisAuthenticationProvider = ref("orbisAuthenticationProvider")
requiresAuthenticationRequestMatcher = ref('filterProcessUrlRequestMatcher')
// This throws error during startup. Unable to init bean
// authenicationManager = ref("authenicationManager")
}
myOAuth2ProviderFilter(OAuth2ProviderFilters) {
//grailsApplication = ref('grailsApplication')
// properties
}
}
I followed some of the concepts from this project: https://github.com/ppazos/cabolabs-ehrserver/
Even if the whole process is executed and securityContext is set with authenticated, when I hit oauth/authorize to get Authorization_Code, it redirects back to '/login/auth'. It still doesn't know that a user is already authenticated.
When you add an authentication provider to the AuthenticationManagerBuilder bean (which comes from AuthenticationConfiguration), the authentication manager bean you declare is not used.
Try:
#Configuration
#EnableGlobalMethodSecurity(securedEnabled=true)
public class CustomWebSecurityConfig {
OrbisAuthenticationProvider lwoAuthProvider;
public CustomWebSecurityConfig() {
//
}
#Bean(name= BeanIds.AUTHENTICATION_MANAGER)
AuthenticationManager authenticationManagerBean() throws Exception {
return new ProviderManager(Arrays.asList(lwoAuthProvider));
}
Your AuthenticationManager bean should get picked up and will be used for method security. You can also #Autowire it in your filter if it is being managed by Spring, or #Autowire it in the #Configuration class that instantiates your filter.
NOTE: the above class WILL NOT create any of the Spring Security filters.
(The filter chain wasn't being created anyway - you didn't annotate your class with #EnableWebSecurity)

Spring TestRestTemplate authentication

I am trying to build Spring Boot test to test rest API, so that I can get Principal from the request and use that to identify the user.
Server returns
{"timestamp":1502014507361,"status":403,"error":"Forbidden","message":"Access
Denied","path":"/hello"}
What am I missing here?
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RestTemplateTest {
#Autowired
TestRestTemplate testRestTemplate;
#Test
public void testit() {
testRestTemplate.withBasicAuth("user", "password");
String responsePayload = testRestTemplate.getForObject("/hello", String.class);
}
#RestController
public class GreetingController {
#RequestMapping("/hello")
public String heipat(Principal principal) {
String string = "hello there";
return string;
}
#Configuration
#EnableWebSecurity
static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().anyRequest().hasRole("USER");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
You need to be authenticated first. like requesting a /login API.
Also you need to make the login API accessible by everyone by doing this:
http.csrf().disable().authorizeRequests()
.antMatchers("/login").permitAll()
When you includes WebSecurityConfig you will have basic usernamerAndPassowrd authentication.

Spring Security Basic authentication for a single path next to token authentication

I have a custom ResourceServerTokenServices in place:
#Configuration
public class CloudSecurityConfig {
#Bean
protected MyResourceServerTokenServices() {
return new MyResourceServerTokenServices();
}
}
Then I have follogin ResourceServerConfigurerAdapter:
#Configuration
#EnableWebSecurity
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityResourceConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.authorizeRequests().accessDecisionManager(accessDecisionManager())
.antMatchers("/h2-console/**").anonymous()
.antMatchers("/health/**").permitAll()
.antMatchers("/v*/api-docs").permitAll().anyRequest()
.authenticated().and().httpBasic().and().headers()
.frameOptions().disable();
}
#Bean
protected UnanimousBased accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> voterList = new ArrayList<>();
WebExpressionVoter expressionVoter = new WebExpressionVoter();
expressionVoter.setExpressionHandler(new OAuth2WebSecurityExpressionHandler());
voterList.add(expressionVoter);
voterList.add(new AuthenticatedVoter());
return new UnanimousBased(voterList);
}
}
Now I need to add basic authentication (with inMemory credentials) for one single endpoint (lets say /myEndpoint**). How can I achieve this?
Thanks!

Spring Security authorize in REST (or form-login) app by HTTP GET

I'm writing backend app based on Spring Boot without any views (templates), because client app will use it's own HTML.
I'm trying change default behavior (HTTP POST) Spring Security form-login authentication - use HTTP GET and POST. Yes, I know, it's bad for security, but it's requirement.
How I can do it?
My app:
Application
package net.company.rest;
#EnableAutoConfiguration
#ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
SecurityConfig
package net.company.rest.config;
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthSuccessHandler authSuccessHandler;
#Autowired
private AuthFailureHandler authFailureHandler;
#Autowired
private AuthEntryPoint authEntryPoint;
// configure security
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
http.exceptionHandling().authenticationEntryPoint(authEntryPoint);
http.formLogin().usernameParameter("user").passwordParameter("pass");
http.formLogin().successHandler(authSuccessHandler).failureHandler(authFailureHandler);
http.logout().permitAll();
http.cors();
http.csrf().disable();
}
// enable security
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
for (int i = 1; i <= 10; i++) {
auth.inMemoryAuthentication().withUser("user" + i).password("user" + i).roles("USER");
}
}
}
AuthEntryPoint
package net.company.rest.component;
#Component
public class AuthEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest req,
HttpServletResponse resp,
AuthenticationException ex) throws IOException, ServletException {
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
UniversalMessage msg = new UniversalMessage(1, "not authenticated");
try {
resp.getWriter().print(new ObjectMapper().writeValueAsString(msg));
} catch (JsonProcessingException e) {
resp.getWriter().print(e.toString());
}
resp.getWriter().flush();
}
}
AuthSuccessHandler
package net.company.rest.component;
#Component
public class AuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest req,
HttpServletResponse resp,
Authentication auth) throws ServletException, IOException {
resp.setStatus(HttpServletResponse.SC_OK);
UniversalMessage msg = new UniversalMessage(0, "auth success");
try {
resp.getWriter().print(new ObjectMapper().writeValueAsString(msg));
} catch (JsonProcessingException e) {
resp.getWriter().print(e.toString());
}
resp.getWriter().flush();
clearAuthenticationAttributes(req);
}
}
AuthFailureHandler
package net.company.rest.component;
#Component
public class AuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest req,
HttpServletResponse resp,
AuthenticationException ex) throws IOException, ServletException {
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
UniversalMessage msg = new UniversalMessage(1, "auth error");
try {
resp.getWriter().print(new ObjectMapper().writeValueAsString(msg));
} catch (JsonProcessingException e) {
resp.getWriter().print(e.toString());
}
resp.getWriter().flush();
}
}
The problem was that above code didn't solve problem, login by HTTP GET didn't work anyway.
There is steps for solve:
Add new class extends UsernamePasswordAuthenticationFilter
package net.company.rest.config.web;
#Slf4j
public class AuthByGetFilter extends UsernamePasswordAuthenticationFilter {
public AuthByGetFilter() {
super();
// change auth parameters
setUsernameParameter("user");
setPasswordParameter("pass");
// allow GET
setPostOnly(false);
setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "GET"));
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
log.debug("Method: {}, Request: {}, params was hidden for security", request.getMethod(), request.getRequestURL());
log.debug("Authentication request failed: {}", failed.toString());
log.debug("Updated SecurityContextHolder to contain null Authentication");
log.debug("Delegating to authentication failure handler " + getFailureHandler());
getRememberMeServices().loginFail(request, response);
getFailureHandler().onAuthenticationFailure(request, response, failed);
}
}
Use this class as filter in the above described SecurityConfig class
...
// configure security
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(authByGetFilter(), UsernamePasswordAuthenticationFilter.class); // add authByGetFilter
...
}
// allow authentication by http GET method
#Bean
public AuthByGetFilter authByGetFilter() throws Exception {
AuthByGetFilter authByGetFilter = new AuthByGetFilter();
authByGetFilter.setAuthenticationManager(authenticationManagerBean());
authByGetFilter.setAuthenticationFailureHandler(authFailureHandler);
authByGetFilter.setAuthenticationSuccessHandler(authSuccessHandler);
return authByGetFilter;
}
...

Implement both http basic and forms login on the same springboot application

I want to implement http basic authentication for the path '/api/' and forms authentication for the paths '/' and '/admin' of my springboot application.
This is my current java config code, but it is not working, any ideas? =)
This code makes all the site to be secured with http basic, not just '/api'. I have found some questions in stackoverflow but they dont seem to solve my issue:
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource datasource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**").authenticated().and()
.httpBasic();
http.authorizeRequests()
.antMatchers("/**").authenticated()
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin().loginPage("/login").permitAll()
.defaultSuccessUrl("/inicio");
http.logout().permitAll();
http.csrf().disable();
}
http.csrf().disable();
}
...
I had the same issue and had to split the Basic and Form Authentication.
#Configuration
#EnableWebSecurity
public class FormSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //
.antMatchers("/**").authenticated() //
.antMatchers("/admin/**").hasRole("ADMIN") //
.and() //
.formLogin().loginPage("/login").defaultSuccessUrl("/inicio").permitAll() //
.and() //
.logout();
}
}
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
public class BasicSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.antMatcher("/api/**") //
.authorizeRequests().anyRequest().authenticated() //
.and() //
.httpBasic();
}
}
http://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#multiple-httpsecurity