I want to write my own LDAP authentication provider. I am extending AbstractUserDetailsAuthenticationProvider, which has a method retrieveUser(String username, UsernamePasswordAuthenticationToken authentication).
I want to override this method and write my own data retrieving method. How to do that in Java? How to make an LDAP query and how connect to the LDAP server? I was searching in Internet but I didn't find anything that helped.
EDIT: 22.01.2013
#Override
protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
LdapUser userDetail = null;
log.entry("retrieveUser", authentication.getPrincipal());
UsernamePasswordAuthenticationToken userToken = authentication;
String userName = userToken.getName();
userName = userName != null ? userName.toLowerCase() : userName;
String password = userToken.getCredentials().toString();
try {
if (password == null || "".equals(password)) {
log.debug("retrieveUser", "no password provided");
throw new AuthenticationCredentialsNotFoundException(
"Invalid login or password");
}
}
catch (AuthenticationCredentialsNotFoundException e) {
log.debug("retrieveUser", "no password provided");
}
// connection with ldap and check retrieved username and password
connect = connection(userName, password);
if (connect) {
log.debug("retrieve user", "correct connection with ldap");
userDetail = new LdapUser();
setUserDetails(userDetail, ctx, username);
} else {
log.error("retrieve user", "Failed connection");
}
log.exit("retrieveUser", "user logged: " + userDetail);
return userDetail;
}
My security.xml file
<http auto-config='true'>
<intercept-url pattern="/**/*.ico" filters="none" />
<intercept-url pattern="/**/*.gif" filters="none" />
<intercept-url pattern="/**/*.jpg" filters="none" />
<intercept-url pattern="/**/*.css" filters="none" />
<intercept-url pattern="/**/*.js" filters="none" />
<intercept-url pattern="/**/*.png" filters="none" />
<intercept-url pattern="/logout.jsp*" filters="none" />
<intercept-url pattern="/index.jsp*" filters="none" />
<intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" />
<logout logout-success-url="/index.jsp"/>
<form-login login-page="/index.jsp"
authentication-failure-url="/error_ldap.jsp"
default-target-url="/main_ldap.jsp" always-use-default-target="true" />
</http>
<authentication-manager>
<authentication-provider ref="ldapAuthenticationProvider">
<password-encoder hash="sha" />
</authentication-provider>
</authentication-manager>
When login is suceed I got redirect to main_ldap.jsp, but if authentication fail, I got this error. I tried to throw exception UsernameNotFoundException instead returning null in retrieveUser method (which is not allowed) but anything happend (only i got this exception).
You can connect to LDAP from java:
http://docs.oracle.com/javase/jndi/tutorial/ldap/security/ldap.html
but spring security already has ldap integration, you can use of the methods described here:
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/ldap.html
...
xml config for using your own UserDetails service is:
<b:bean id="userDetailsService" class="your.class.here">
</b:bean>
<authentication-provider user-service-ref="userDetailsService">
</authentication-provider>
Related
I am new with RESTful web services in spring,whenever i am requesting the URL through postman,i am getting random generated token from server side, here you are controller code,through this i am getting random generated token.
#RequestMapping(value = "/api/authenticate", method = RequestMethod.POST)
public #ResponseBody
Result doLogIn(#RequestParam("BulkData") String bulkData, HttpServletResponse response) throws Exception {
ObjectMapper mapper = new ObjectMapper();
JsonNode actualObj = null;
try {
actualObj = mapper.readTree(bulkData);
} catch (IOException e1) {
e1.printStackTrace();
return new Result("Invalid Request", ResultCodes.LOGIN_FAILURE);
}
String userName = actualObj.get("userName").asText();
String password = actualObj.get("password").asText();
logger.debug("[REST]: Attempting login for -> " + userName);
UserDetails details = userDetailService.loadUserByUsername(userName);
// validate password
if (details != null && !details.getPassword().equals(password)) {
logger.debug("[REST]: Invalid username/password");
try {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid username/password");
} catch (IOException e) {
e.printStackTrace();
}
return new Result("Invalid username or password", ResultCodes.LOGIN_FAILURE);
}
// Generate token. ATM, use only username
String generatedToken = Jwts.builder().setSubject(userName)
.setIssuedAt(new Date())
// set token expiration time
.setExpiration(new Date(System.currentTimeMillis() + Config.TOKEN_EXPIRY_PERIOD))
.signWith(SignatureAlgorithm.HS256, servletContext.getInitParameter("API_SECRET_KEY"))
.compact();
// provide token to user in form of a Http Header
response.addHeader(Config.AUTH_TOKEN_HEADER_NAME, generatedToken);
return new Result("Login Success", ResultCodes.LOGIN_SUCCESS_TOKEN_GENERATED);
}
and here is the code for authorization , to do so i am using AuthenticationTokenProcessingFilter,
public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
#Autowired
private UserDetailService userDetailService;
#Autowired
private AuthenticationManager authenticationManager;
private static Logger logger = Logger.getLogger(AuthenticationTokenProcessingFilter.class);
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
// Exclude login URL
if(req.getRequestURI().endsWith("/api/authenticate")) {
chain.doFilter(req, response);
return;
}
// Client must send token in header
String authHeader = req.getHeader(Config.AUTH_TOKEN_HEADER_NAME);
if (authHeader == null) {
logger.error("[REST]: Authentication header was null...");
throw new ServletException("Missing or invalid Authorization header.");
}
// Parse token, fetch user and reload Security Context
try {
String SECRET_KEY = getServletContext().getInitParameter("API_SECRET_KEY");
Jws<Claims> claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(authHeader);
Claims claim = claims.getBody();
String userName = claim.getSubject();
logger.debug("[REST]: Token of user -> " + userName + " expires: " + claim.getExpiration());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, userDetailService.loadUserByUsername(userName).getPassword());
token.setDetails(new WebAuthenticationDetails(req));
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (SignatureException e) {
logger.debug("[REST]: Invalid token");
throw new ServletException("Invalid token.");
}
chain.doFilter(req, response);
// clear security context now because we are going for Stateless Web Services
SecurityContextHolder.getContext().setAuthentication(null);
}
now i want to use this generated token to call this method ,
#RequestMapping(value="/api/admin/getEmployeerole", method=RequestMethod.POST)
public List<EmployeeRole> EmployeeRoleList() {
List<EmployeeRole> getRole=employeeRoleService.getAll();
return getRole;
}
now what is happening here when i am writing this URL to postman and adding generated token into header ,and i have used authorization type (No auth), i have also tried with basic Authorization, still my request is going to customAuthenticationEntrypoint and it throws Access denied exception,that the user Role is annonymous, at server side i am getting status 401 unauthorized.it would be great if someone can help to get over from this..
here You are my spring security configuration..
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="hp.bootmgr.authentication.provider" />
<http pattern="/resources/**" security="none" />
<http pattern="/api/**" realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="customAuthenticationEntryPoint">
<!-- <custom-filter position="FORM_LOGIN_FILTER" /> -->
<intercept-url pattern="/api/authenticate" access="permitAll()" />
<intercept-url pattern="/api/admin/**" access="hasRole('ADMIN')" />
<intercept-url pattern="/api/user/**" access="hasAnyRole('ADMIN', 'EMPLOYEE')" />
<intercept-url pattern="/api/member/**" access="hasAnyRole('ADMIN', 'MEMBER')" />
<!--<form-login
login-page="/api/authenticate"
login-processing-url="/j_spring_security_check"
username-parameter="userName"
password-parameter="password" />-->
<logout logout-url="/logout"/>
<csrf disabled="true"/>
</http>
<http auto-config="true" use-expressions="true" entry-point-ref="authenticationEntryPoint">
<access-denied-handler error-page="/403" />
<intercept-url pattern="/login" access="true"/>
<intercept-url pattern="/admin/**" access="hasRole('ADMIN')" />
<!-- Allow access to user pages to admin, as long as there is no more other rules-->
<intercept-url pattern="/user/**" access="hasAnyRole('ADMIN', 'EMPLOYEE')" />
<intercept-url pattern="/member/**" access="hasAnyRole('ADMIN', 'MEMBER')" />
<form-login
login-page="/login"
default-target-url="/home"
authentication-failure-url="/login?failed=1"
login-processing-url="/j_spring_security_check"
username-parameter="userName"
password-parameter="password" />
<logout logout-success-url="/login?logout=1" invalidate-session="true" logout-url="/logout"/>
<!-- enable csrf protection -->
<csrf disabled="true"/>
<session-management>
<concurrency-control max-sessions="1" expired-url="/login" />
</session-management>
</http>
<beans:bean id="customAuthenticationEntryPoint" class="hp.bootmgr.web.services.authentication.CustomAuthenticationEntryPoint" />
<beans:bean id="authenticationTokenProcessingFilter" class="hp.bootmgr.web.services.authentication.AuthenticationTokenProcessingFilter" />
<beans:bean id="authenticationEntryPoint" class="hp.bootmgr.security.AuthenticationEntryPoint">
<beans:constructor-arg name="loginUrl" value="/login"/>
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userDetailService" />
</authentication-manager>
</beans:beans>
This should be a simple solution but I can't seem to figure it out.
Problem: Every time I try to login as either user or admin, the username and password always return "access denied for this user" (even though the usernames and the roles are indeed in the database).
Here are my files:
LoginController:
#Controller
public class LoginController {
#RequestMapping("login")
public ModelAndView getLoginForm(
#RequestParam(required = false) String authfailed, String logout,
String denied) {
String message = "";
if (authfailed != null) {
message = "Invalid username of password, try again !";
} else if (logout != null) {
message = "Logged Out successfully, login again to continue !";
} else if (denied != null) {
message = "Access denied for this user !";
}
return new ModelAndView("login", "message", message);
}
#RequestMapping("user")
public String geUserPage() {
return "user";
}
#RequestMapping("admin")
public String geAdminPage() {
return "admin";
}
#RequestMapping("403page")
public String ge403denied() {
return "redirect:login?denied";
}
}
security-servlet.xml:
<http auto-config="true" use-expressions="true">
<access-denied-handler error-page="/403page" />
<intercept-url pattern="/anonymous" access="isAnonymous"/>
<intercept-url pattern="/user**" access="hasRole('ROLE_USER')" />
<intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" />
<form-login login-page='/login' username-parameter="username"
password-parameter="password" default-target-url="/user"
authentication-failure-url="/login?authfailed" />
<logout logout-success-url="/login?logout" />
</http>
<authentication-manager>
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password, enabled from users where username=?"
authorities-by-username-query="select username, role from user_roles where username =? " />
</authentication-provider>
</authentication-manager>
Solved this by using the right security version in pom.xml
I've changed the default Authentication Provider for a Custom one.
This is my AuthenticationProvider
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private ParamsProperties paramsProperties;
#SuppressWarnings("unchecked")
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//Check username and passwd
String user = (String) authentication.getPrincipal();
String pass = (String) authentication.getCredentials();
if(StringUtils.isBlank(user) || StringUtils.isBlank(pass) ){
throw new BadCredentialsException("Incorrect username/password");
}
//Create SSO
SingleSignOnService service = new SingleSignOnService(paramsProperties.getServicesServer());
try {
//Check logged
service.setUsername(authentication.getName());
service.setPassword(authentication.getCredentials().toString());
ClientResponse response = service.call();
String result = response.getEntity(String.class);
ObjectMapper mapper = new ObjectMapper();
Map<String,Object> map = mapper.readValue(result, new TypeReference<Map<String,Object>>() {} );
//Read code
String code = (String)map.get("code");
log.debug(" ** [Authenticate] Result: " + code );
for (String s : (List<String>)map.get( "messages" ) ) {
log.debug(" [Authenticate] Message: " + s );
}
if ( code.equals( "SESSION_CREATED" ) || code.equals( "SESSION_UPDATED" ) || code.equals( "SESSION_VERIFIED" ) ) {
UsernamePasswordAuthenticationToken tokenSSO = LoginHelper.getuserSringTokenFromAuthService(map);
return tokenSSO;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
throw new AuthenticationServiceException( e.getMessage() );
}
}
public boolean supports(Class authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
And this is my security.xml
<http>
<form-login default-target-url ="/Login.html" always-use-default-target="true" login-page="/Login.html" login-processing-url="/j_spring_security_check"
authentication-failure-url="/Login.html" />
<http-basic />
<logout logout-success-url="/Login.html" />
</http>
<beans:bean id="localeFilter" class="com.mycomp.comunes.server.spring.controller.login.MyLocaleFilter" lazy-init="true">
<custom-filter position="LAST"/>
</beans:bean>
<beans:bean id="authenticationProvider" class="com.indra.rfef.comunes.server.spring.manager.autenticacion.CustomAuthenticationProvider">
<custom-authentication-provider />
</beans:bean>
It gets over my CustomAuthenticationProvider, and authenticates correctly the user. But when returning tokenSSO, of type UsernamePasswordAuthenticationToken, it seems it's not saving the user on the Security Context, and when I redirect the user (on the callback of the authenticate) to the index.html, I get redirected back to Login.html.
Why could this happen? I'm I forgetting something?
Please fix your configuration:
<http>
<intercept-url pattern="/Login*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER"/>
<form-login login-page="/Login.html" login-processing-url="/j_spring_security_check" authentication-failure-url="/Login.html" />
<http-basic />
<logout logout-success-url="/Login.html" />
</http>
Remove default-target-url ="/Login.html". It makes the redirection after login to the same login page. The default is /.
Add security on all URLs <intercept-url pattern="/**" access="ROLE_USER"/>
Do not remove the anonymous access from the login page
Why you need BasicAuthentication? Remove it if not required: <http-basic />
I'm trying to implement spring security 3.1.0.M1 and I'm unable to get my application to set the Authentication.getPrincipal to my custom UserDetails implementation. It always returns a principal of "guest" when I try to get the logged in user. See getLoggedInUser method below.
In Users.java (UserDetails impl) the getAuthorities method never gets called and maybe that's why the user_role doesn't get assigned.
to Maybe I've misconfigured something...I've attached an outline of my implementation hoping someone can spot my error. Thanks for the assistance!
public static Users getLoggedInUser() {
Users user = null;
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
Object principal = auth.getPrincipal();
if (principal instanceof Users) {
user = (Users) principal;
}
}
return user;
}
security context file(removed the xml and schema definitions):
<global-method-security secured-annotations="enabled">
</global-method-security>
<http security="none" pattern="/services/rest-api/1.0/**" />
<http security="none" pattern="/preregistered/**" />
<http access-denied-page="/auth/denied.html">
<intercept-url
pattern="/**/*.xhtml"
access="ROLE_NONE_GETS_ACCESS" />
<intercept-url
pattern="/auth/**"
access="ROLE_ANONYMOUS,ROLE_USER" />
<intercept-url
pattern="/auth/*"
access="ROLE_ANONYMOUS" />
<intercept-url
pattern="/**"
access="ROLE_USER" />
<form-login
login-processing-url="/j_spring_security_check.html"
login-page="/auth/login.html"
default-target-url="/registered/home.html"
authentication-failure-url="/auth/login.html?_dc=45" />
<logout logout-url="/auth/logout.html"
logout-success-url="/" />
<anonymous username="guest" granted-authority="ROLE_ANONYMOUS"/>
<remember-me user-service-ref="userManager" key="valid key here"/>
</http>
<!-- Configure the authentication provider -->
<authentication-manager>
<authentication-provider user-service-ref="userManager">
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
UserDetails Implementation (Users.java):
public class Users implements Serializable, UserDetails {
public Collection<GrantedAuthority> getAuthorities() {
List<GrantedAuthority> auth = new ArrayList<GrantedAuthority>();
auth.add(new GrantedAuthorityImpl("ROLE_USER"));
return auth;
}
}
user-service-ref="userManager" (UserManagerImpl.java):
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
Users user = null;
try {
user = userDAO.findByUsername(username);
} catch (DataAccessException ex) {
throw new UsernameNotFoundException("Invalid login", ex);
}
if (user == null) {
throw new UsernameNotFoundException("User not found.");
}
return user;
}
Are you not getting compilation error on this line: auth.add("ROLE_USER");?
I think it should be : auth.add(new SimpleGrantedAuthority("ROLE_USER"));
I use Spring MVC [version: 2.5] and Security[version: 2.0.4].
My problem looks like that:
First login into my app with UserA login and Password -> OK
Logout UserA, UserB is login in.
UserB login + password works fine, I'm in app and UserB ROLE is on. [no access for admin session if he's no admin]
HOWEVER!
I use this code to get data from database, about login user:
userejb.findUserByUsername(SecurityContextHolder.getContext().getAuthentication().getName());
and my user is not UserB but UserA...
How can i fix it? What i did wrong?
My security configuration:
<bean id="userDetailsService" class="pl.tzim.jlp.security.CustomUserDetailsServiceImpl" />
<http auto-config='true'>
<!-- login panel dostepny dla wszystkich chetnych!-->
<intercept-url pattern="/login.action" filters="none"/>
<intercept-url pattern="/index.jsp" filters="none"/>
<intercept-url pattern="/CS/**" filters="none" />
<intercept-url pattern="/JS/**" filters="none" />
<intercept-url pattern="/grafiki/**" filters="none" />
<intercept-url pattern="/free/**" access="" />
<intercept-url pattern="/admin/**" access="ROLE_ADMIN"/>
<intercept-url pattern="/teacher/**" access="ROLE_TEACHER, ROLE_ADMIN"/>
<intercept-url pattern="/all/**" access="ROLE_STUDENT, ROLE_TEACHER, ROLE_ADMIN"/>
<intercept-url pattern="/student/**" access="ROLE_STUDENT, ROLE_TEACHER, ROLE_ADMIN"/>
<intercept-url pattern="/login/**" access="ROLE_STUDENT, ROLE_TEACHER, ROLE_ADMIN" />
<intercept-url pattern="/*" access="ROLE_STUDENT, ROLE_TEACHER, ROLE_ADMIN" />
<form-login login-page='/free/login.action' authentication-failure-url="/free/login.action?why=error" default-target-url="/free/index.action"/>
<logout logout-success-url="/free/login.action?why=logout"/>
<concurrent-session-control max-sessions="99" exception-if-maximum-exceeded="true"/>
</http>
<authentication-provider user-service-ref='userDetailsService' />
My loginUser class and method:
#SessionAttributes(types = {CustomUser.class}, value = "{logedUser}")
public class CustomUserDetailsServiceImpl implements UserDetailsService {
#Autowired
public UserDAO userdao;
public CustomUser logedUser;
#Transactional(readOnly = true)
#Override
public CustomUser loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
try {
pl.tzim.jlp.model.user.User user = this.userdao.findUserByUsername(username);
String password = user.getPassword();
String role = user.getAuthority().getRolename();
boolean enabled = true;
logedUser = new CustomUser(user.getId(), username, password, enabled, new GrantedAuthority[]{new GrantedAuthorityImpl(role)});
return logedUser;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
public class CustomUser extends User{
private Long id;
public CustomUser(Long id, String username, String password, boolean isEnabled, GrantedAuthority[] authorities){
super(username, password, isEnabled, true, true, true, authorities);
this.setId(id);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
I suggest that you set the logging level to DEBUG and examine the logs to see what is happening.
Why you keep the last user in this attribute?
public CustomUser logedUser;
Looks like it will be overriden with every login.
And why you put it into the Session when Spring Security already stored it in SecurityContextHolder.
As Stephen said we need the log output.