Spring Rest template with http client for NTLM authentication - spring

We have a web service deployed in IIS server which authenticate based on NTLM authentication.
When i try to access the web service by passing username and password in httpCleint UserNamePasswordCredentials, am getting warnings as
NTLM authentication error: Credentials cannot be used for NTLM authentication: org.apache.http.auth.UsernamePasswordCredentials
Please clarify how to user http client with spring rest template to pass the NTLM authentication with user name and password.
Note:Though am getting the warning message, am also getting response.

Just define the following class.
public class NtlmAuthenticator extends Authenticator {
private final String username;
private final char[] password;
public NtlmAuthenticator(final String username, final String password) {
super();
this.username = username;
this.password = password.toCharArray();
}
#Override
public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication(username, password));
}
}
then add the following code.Thats it.It started working.
NtlmAuthenticator authenticator = new NtlmAuthenticator(userName,
password);
Authenticator.setDefault(authenticator);

Related

Secure Spring Boot API without user authentication

I'm currently developing a few services with spring boot and some security concerns came in mind. All the exposed endpoints are public and do not require any user/password authentication. However, these services cannot be easily accessible by a caller other than our front-end application, since we must gather some user information through a form, in which a captcha performs a validation. Because of that, we need to ensure that the services are only invoked by this front-end application and that fake requests are denied.
Due to these requirements, i initially thought that making the endpoints accessible via https was enough. Notwithstanding, the possibility of replay attacks and spoofing still concerns me.
So, reading a few articles i came up with the following draft:
Please refer the client as the front-end application.
client and server should share a key-pair (public and private keys).
For every request, the following must be satisfied:
client creates a unique nonce (random number)
client generates a HMAC-SHA1 token with the shared private key
token = hmac('sha1', private_key, public_key + timestamp + nonce);
client must send the public_key, timestamp, nonce and token in header
upon receiving a request, the server checks if all the header params are present and then calculates the same hmac-sha1 token and compares with the received value from the client.
the nonce is then added to a cache manager, so that duplicated requisitions are discarded.
if any of header parameters are missing or if the calculated token is different from the one sent by the client, the requesition is also discarded.
Is this an appropriate approach? Are the benefits of such overhead worth?
These are the codes i currently have:
#Service
public class APIAuthenticationManager implements AuthenticationManager {
#Value("${security.http.api_key}")
private String apiKeyValue;
#Value("${security.http.api_key_header}")
private String apiKeyRequestHeader;
#Value("${security.valid_timestamp.thresold}")
private String timestampThresold;
#Value("${security.valid_timestamp.header}")
private String timestampHeader;
#Value("${security.nonce.header}")
private String nonce;
#Value("${security.token.header}")
private String tokenHeader;
#Value("${security.private_key}")
private String privateKey;
#Override
public Authentication authenticate(Authentication authentication) {
HttpServletRequest request = (HttpServletRequest) authentication.getPrincipal();
if (!apiKeyValue.equals(request.getParameter(apiKeyRequestHeader))) {
throw new BadCredentialsException("The API key was not found or not the expected value.");
}
String timestamp = request.getParameter(timestampHeader);
if(timestamp == null) {
throw new BadCredentialsException("Timestamp was not found or its value is invalid.");
}
Date requestIssueDate = Util.parseDate(timestamp, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
if(requestIssueDate == null) {
throw new BadCredentialsException("Timestamp was not found or its value is invalid.");
}
long expired = System.currentTimeMillis() - Integer.valueOf( timestampThresold );
if (requestIssueDate.getTime() > expired) {
throw new BadCredentialsException("Timestamp was not found or its value is invalid.");
}
// HMAC('SHA1', 'API_KEY', 'TOKEN GENERATED IN CLIENT');
// TOKEN GENERATED IN CLIENT = HMAC('SHA1', 'API_KEY', 'SECRET_KEY + TIMESTAMP + NONCE');
String tokenFromClient = request.getParameter(tokenHeader);
String calculatedToken = HMACSignatureUtil.calculateHMAC(privateKey, apiKeyValue + timestamp + nonce);
if(!tokenFromClient.equals(calculatedToken)) {
throw new BadCredentialsException("Invalid token.");
}
authentication.setAuthenticated(true);
return authentication;
}
This is the ConfigurerAdapter
#Autowired
private APIAuthenticationManager apiAuthenticationManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
APIKeyAuthFilter filter = new APIKeyAuthFilter();
filter.setAuthenticationManager( apiAuthenticationManager );
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilter(filter).authorizeRequests().anyRequest().authenticated();
http.requiresChannel()
.anyRequest(). requiresSecure();
}

How to retrieve attributes and username sent by the CAS server with Spring Security

I have a spring boot application, which is MVC in nature. All page of this application are being authenticated by CAS SSO.
I have used "spring-security-cas" as described at https://www.baeldung.com/spring-security-cas-sso
Everything working fine as expected. However, I have one problem - that is, I cannot retrieve attributes
and username sent by the CAS server in the following #Bean. What need I do to retrieve all the attributes
and and username sent by the CAS server?
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(ticketValidator());
provider.setUserDetailsService(
s -> new User("casuser", "Mellon", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
provider.setKey("CAS_PROVIDER_LOCALHOST_9000");
return provider;
}
First you will need to configure the attributeRepository source and the attributes to be retrieved, in attributeRepository section in CAS server, like:
cas.authn.attributeRepository.jdbc[0].singleRow=false
cas.authn.attributeRepository.jdbc[0].sql=SELECT * FROM USERATTRS WHERE {0}
cas.authn.attributeRepository.jdbc[0].username=username
cas.authn.attributeRepository.jdbc[0].role=role
cas.authn.attributeRepository.jdbc[0].email=email
cas.authn.attributeRepository.jdbc[0].url=jdbc:hsqldb:hsql://localhost:9001/xdb
cas.authn.attributeRepository.jdbc[0].columnMappings.attrname=attrvalue
cas.authn.attributeRepository.defaultAttributesToRelease=username,email,role
Check this example from CAS blog.
Then you need to implement an AuthenticationUserDetailsService at the service to read attributes returned from CAS authentication, something like:
#Component
public class CasUserDetailService implements AuthenticationUserDetailsService {
#Override
public UserDetails loadUserDetails(Authentication authentication) throws UsernameNotFoundException {
CasAssertionAuthenticationToken casAssertionAuthenticationToken = (CasAssertionAuthenticationToken) authentication;
AttributePrincipal principal = casAssertionAuthenticationToken.getAssertion().getPrincipal();
Map attributes = principal.getAttributes();
String uname = (String) attributes.get("username");
String email = (String) attributes.get("email");
String role = (String) attributes.get("role");
String username = authentication.getName();
Collection<SimpleGrantedAuthority> collection = new ArrayList<SimpleGrantedAuthority>();
collection.add(new SimpleGrantedAuthority(role));
return new User(username, "", collection);
}
}
Then, adjust your authenticationProvider with provider.setAuthenticationUserDetailsService(casUserDetailService);

Ldap Auth as Rest Controller

I have configured a remote Ldap server, I have a frontend and the desired behavior is: When the user fills the login form in frontend, I want to send credentials to backend via a controller then backend should perform a lookup to my ldap server and return a response to identify the user like his id and null if user is not found.
I am having a hard time about wrapping my head around the concept and all examples are either using a local ldap or redirecting to login form on backend. I do not want the login form on backend or secure some endpoints.
This is what I am doing in my project:
in application.properties file
server,protocol=http://
server.host.name=
server.ip=
server.port=
server.url=
Then from RESTController I am calling this service:
#Service
public class ldapService
{
#Value("${ldap.server.protocol}")
private String LDAP_SERVER_PROTOCOL;
#Value("${ldap.server.ip}")
private String LDAP_SERVER_IP;
#Value("${ldap.server.port}")
private int LDAP_SERVER_PORT;
#Value("${ldap.service.url}")
private String LDAP_SERVICE_URL;
public String authenticate(LoginDto loginDto){
UserCredentials userCredentials = new UserCredentials(loginDto.getUserName(), loginDto.getPassword());
RestTemplate restTemplate = new RestTemplate();
HttpEntity<UserCredentials> httpEntity = new HttpEntity<UserCredentials>(userCredentials);
final String FINAL_URL = LDAP_SERVER_PROTOCOL + LDAP_SERVER_IP + LDAP_SERVER_PORT + LDAP_SERVICE_URL;
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(FINAL_URL);
ResponseEntity<ResponseDto> exchange = restTemplate.exchange(builder.build().encode().toUri(), HttpMethod.POST,
httpEntity, ResponseDto.class);
HttpStatus statusCode = exchange.getStatusCode();
ResponseDto responseDto = exchange.getBody();
// check if response OK and is user validated.
if (statusCode == HttpStatus.OK)
{
//switch according to HttpStatus
}

Spring OAuth2.0: Getting User Roles based on ClientId (Authorization Code Grant Type)

I have a setup of spring boot OAuth for AuthServer and it is resposible for serving a number of few resource server for authentication using spring-security-jwt.
My problem is while authenticating I need to load the roles of a user but specific to the clientId.
eg: If user1 have roles ROLE_A, ROLE_B for client1 and ROLE_C, ROLE_D for client2, then when the user logins either using client1 or client2 he is able to see all the four roles ie. ROLE_A, ROLE_B, ROLE_C, ROLE_D because I am getting roles based on username.
If I need to have a role based on the client then I need clientId.
FYI,
I am using the authorization code flow for authentication.
I have seen similar question but that is based on password grant but I am trying on authorization code flow and that solution doesn't work for me.
Password grant question link
Below is my code where I need clientId
MyAuthenticationProvider.java
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
String userName = ((String) authentication.getPrincipal()).toLowerCase();
String password = (String) authentication.getCredentials();
String clientId = ? // how to get it
....
}
}
MyUserDetailsService.java
#Override
public UserDetails loadUserByUsername(String username) {
String clientId = ? // how to get it
....
}
}
You probably need to see OAuth2Authentication in Spring-security. When your client is authenticated by oauth2, then your "authentication" is actually instance of OAuth2Authentication that eventually implements Authentication.
If you see the implementation of OAuth2Authentication, it's done as below;
public Object getPrincipal() {
return this.userAuthentication == null ? this.storedRequest.getClientId() : this.userAuthentication
.getPrincipal();
}
so if request included "clientId', then you should be able to get clientId by calling getPrincipal() and typecasting to String as long as your request didn't include user authentication.
For your 2nd case, username is actually considered as clientId. You need to call in-memory, RDBMS, or whatever implementation that has clientId stored and returns ClientDetails. You'll be able to have some idea by looking into Spring security's ClientDetailsUserDetailsService class.
Since I didn't get any appropriate solution for my question, I am posting the solution that I used after digging source code and research.
MyJwtAccessTokenConverter.java (Extend JwtAccessTokenConverter and implement enhance method)
public class OAuthServerJwtAccessTokenConverter extends JwtAccessTokenConverter {
....
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String clientId = authentication.getOAuth2Request().getClientId();
// principal can be string or UserDetails based on whether we are generating access token or refreshing access token
Object principal = authentication.getUserAuthentication().getPrincipal();
....
}
....
}
Info:
In enhance method, we will get clientId from authentication.getOAuth2Request() and userDetails/user_name from authentication.getUserAuthentication().
Along with JwtAccessTokenConverter, AuthenticationProvider and UserDetailsService are required for authentication in generating access token step and refresh token step respectively.
get authorization header from request then parse from base64 to get the client-id.
something like this:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String authHeader = request
.getHeader("Authorization");

How to design a good JWT authentication filter

I am new to JWT. There isn't much information available in the web, since I came here as a last resort. I already developed a spring boot application using spring security using spring session. Now instead of spring session we are moving to JWT. I found few links and now I can able to authenticate a user and generate token. Now the difficult part is, I want to create a filter which will be authenticate every request to the server,
How will the filter validate the token? (Just validating the signature is enough?)
If someone else stolen the token and make rest call, how will I verify that.
How will I by-pass the login request in the filter? Since it doesn't have authorization header.
Here is a filter that can do what you need :
public class JWTFilter extends GenericFilterBean {
private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class);
private final TokenProvider tokenProvider;
public JWTFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,
ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = this.resolveToken(httpServletRequest);
if (StringUtils.hasText(jwt)) {
if (this.tokenProvider.validateToken(jwt)) {
Authentication authentication = this.tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(servletRequest, servletResponse);
this.resetAuthenticationAfterRequest();
} catch (ExpiredJwtException eje) {
LOGGER.info("Security exception for user {} - {}", eje.getClaims().getSubject(), eje.getMessage());
((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
LOGGER.debug("Exception " + eje.getMessage(), eje);
}
}
private void resetAuthenticationAfterRequest() {
SecurityContextHolder.getContext().setAuthentication(null);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(SecurityConfiguration.AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
String jwt = bearerToken.substring(7, bearerToken.length());
return jwt;
}
return null;
}
}
And the inclusion of the filter in the filter chain :
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public final static String AUTHORIZATION_HEADER = "Authorization";
#Autowired
private TokenProvider tokenProvider;
#Autowired
private AuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTFilter customFilter = new JWTFilter(this.tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
// #formatter:off
http.authorizeRequests().antMatchers("/css/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/authenticate").permitAll()
.anyRequest().fullyAuthenticated()
.and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll()
.and().logout().permitAll();
// #formatter:on
http.csrf().disable();
}
}
The TokenProvider class :
public class TokenProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TokenProvider.class);
private static final String AUTHORITIES_KEY = "auth";
#Value("${spring.security.authentication.jwt.validity}")
private long tokenValidityInMilliSeconds;
#Value("${spring.security.authentication.jwt.secret}")
private String secretKey;
public String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream().map(authority -> authority.getAuthority()).collect(Collectors.joining(","));
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime expirationDateTime = now.plus(this.tokenValidityInMilliSeconds, ChronoUnit.MILLIS);
Date issueDate = Date.from(now.toInstant());
Date expirationDate = Date.from(expirationDateTime.toInstant());
return Jwts.builder().setSubject(authentication.getName()).claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS512, this.secretKey).setIssuedAt(issueDate).setExpiration(expirationDate).compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody();
Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(AUTHORITIES_KEY).toString().split(",")).stream()
.map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
LOGGER.info("Invalid JWT signature: " + e.getMessage());
LOGGER.debug("Exception " + e.getMessage(), e);
return false;
}
}
}
Now to answer your questions :
Done in this filter
Protect your HTTP request, use HTTPS
Just permit all on the /login URI (/authenticate in my code)
I will focus in the general tips on JWT, without regarding code implemementation (see other answers)
How will the filter validate the token? (Just validating the signature is enough?)
RFC7519 specifies how to validate a JWT (see 7.2. Validating a JWT), basically a syntactic validation and signature verification.
If JWT is being used in an authentication flow, we can look at the validation proposed by OpenID connect specification 3.1.3.4 ID Token Validation. Summarizing:
iss contains the issuer identifier (and aud contains client_id if using oauth)
current time between iat and exp
Validate the signature of the token using the secret key
sub identifies a valid user
If someone else stolen the token and make rest call, how will I verify that.
Possesion of a JWT is the proof of authentication. An attacker who stoles a token can impersonate the user. So keep tokens secure
Encrypt communication channel using TLS
Use a secure storage for your tokens. If using a web front-end consider to add extra security measures to protect localStorage/cookies against XSS or CSRF attacks
set short expiration time on authentication tokens and require credentials if token is expired
How will I by-pass the login request in the filter? Since it doesn't have authorization header.
The login form does not require a JWT token because you are going to validate the user credential. Keep the form out of the scope of the filter. Issue the JWT after successful authentication and apply the authentication filter to the rest of services
Then the filter should intercept all requests except the login form, and check:
if user authenticated? If not throw 401-Unauthorized
if user authorized to requested resource? If not throw 403-Forbidden
Access allowed. Put user data in the context of request( e.g. using a ThreadLocal)
Take a look at this project it is very good implemented and has the needed documentation.
1. It the above project this is the only thing you need to validate the token and it is enough. Where token is the value of the Bearer into the request header.
try {
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
}
catch (final SignatureException e) {
throw new ServletException("Invalid token.");
}
2. Stealing the token is not so easy but in my experience you can protect yourself by creating a Spring session manually for every successfull log in. Also mapping the session unique ID and the Bearer value(the token) into a Map (creating a Bean for example with API scope).
#Component
public class SessionMapBean {
private Map<String, String> jwtSessionMap;
private Map<String, Boolean> sessionsForInvalidation;
public SessionMapBean() {
this.jwtSessionMap = new HashMap<String, String>();
this.sessionsForInvalidation = new HashMap<String, Boolean>();
}
public Map<String, String> getJwtSessionMap() {
return jwtSessionMap;
}
public void setJwtSessionMap(Map<String, String> jwtSessionMap) {
this.jwtSessionMap = jwtSessionMap;
}
public Map<String, Boolean> getSessionsForInvalidation() {
return sessionsForInvalidation;
}
public void setSessionsForInvalidation(Map<String, Boolean> sessionsForInvalidation) {
this.sessionsForInvalidation = sessionsForInvalidation;
}
}
This SessionMapBean will be available for all sessions. Now on every request you will not only verify the token but also you will check if he mathces the session (checking the request session id does matches the one stored into the SessionMapBean). Of course session ID can be also stolen so you need to secure the communication. Most common ways of stealing the session ID is Session Sniffing (or the Men in the middle) and Cross-site script attack. I will not go in more details about them you can read how to protect yourself from that kind of attacks.
3. You can see it into the project I linked. Most simply the filter will validated all /api/* and you will login into a /user/login for example.

Resources