I am building a MVC3 web application with WIF and ADFS 2.0. What I want to do is to provide a change password-function for my users, they will be changing their AD password from this web application. Since I am in the development stage, I have to be able to change AD password from my development computer (outside domain). Later, permissions will be delegated to a user that runs the service with sufficient access.
I want to do this role based, without typing in username and password and I can't seem to find any resources which points me in the right direction.
Any suggestions?
There isn't anything specific in WIF or AD FS for changing user passwords. You'll have to use the standard AD functionality provided in the System.DirectoryServices namespace.
Here's some sample code for changing a password in AD:
internal UserPrincipal GetUser(string userName)
{
PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, "YourADController",
"YourADContainer",
"ADAdminUser", "ADAdminPassword");
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, userName);
return user;
}
internal void ResetPassword(string userName, string newPassword)
{
try
{
//
// Update normal AD attributes
//
UserPrincipal user = GetUser(userName);
user.SetPassword(newPassword);
}
catch (PasswordException)
{
throw new Exception("Password does not meet complexity requirements");
}
}
internal void SetPassword(string userName, string oldPassword, string newPassword)
{
try
{
//
// Update normal AD attributes
//
UserPrincipal user = GetUser(userName);
user.ChangePassword(oldPassword, newPassword);
}
catch (PasswordException)
{
throw new Exception("Password does not meet complexity requirements");
}
}
Related
I want to implement this example using Keyclock server with Spring Security 5.
I'm going to use OAuth2.0 authentication with JWT token. I'm interested how I can get the current logged in user into the Rest Endpoint?
I have configured Spring Security not to store user sessions using http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);.
One possible way is to use this code:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
But I don't know is it going to work. Can someone give some advice for that case?
SecurityContextHolder, SecurityContext and Authentication Objects
By default, the SecurityContextHolder uses a ThreadLocal to store these details, which means that the security context is always available to methods in the same thread of execution. Using a ThreadLocal in this way is quite safe if care is taken to clear the thread after the present principal’s request is processed. Of course, Spring Security takes care of this for you automatically so there is no need to worry about it.
SessionManagementConfigurer consist of isStateless() method which return true for stateless policy. Based on that http set the shared object with NullSecurityContextRepository and for request cache NullRequestCache. Hence no value will be available within HttpSessionSecurityContextRepository. So there might not be issue with invalid/wrong details for user with static method
Code:
if (stateless) {
http.setSharedObject(SecurityContextRepository.class,
new NullSecurityContextRepository());
}
if (stateless) {
http.setSharedObject(RequestCache.class, new NullRequestCache());
}
Code:
Method to get user details
public static Optional<String> getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
}
private static String extractPrincipal(Authentication authentication) {
if (authentication == null) {
return null;
} else if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
}
public static Optional<Authentication> getAuthenticatedCurrentUser() {
log.debug("Request to get authentication for current user");
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication());
}
sessionManagement
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
You might like to explore Methods with Spring Security to get current user details with SessionCreationPolicy.STATELESS
After the service validate the token, you can parse it, and put it into the securitycontext, it can contains various data, so you have to look after it what you need. For example, subject contains username etc...
SecurityContextHolder.getContext().setAuthentication(userAuthenticationObject);
The SecurityContextHolder's context maintain a ThreadLocal entry, so you can access it on the same thread as you write it in the question.
Note that if you use reactive (webflux) methodology, then you have to put it into the reactive context instead.
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);
I am creating an application using Spring with Oauth2 as a backend for two apps (provider app and a consumer app). I have two different types of users; Providers, and consumers, each with its own db table.
The problem I am facing is that I cannot find a way to know if the request is coming from a provider or a customer, as each one will be in a different db table.
The username is Not unique between the two tables. So, a provider and a consumer can have the same username (and password).
I think any of the following solutions will suffice, however, I can’t find any way to implement any of them.
Having two different endpoints for each user class. e.g. “/provider/oauth/token” and “/consumer/oauth/token”. Each with its custom authentication manager.
Or: Having two authorization servers in the same Spring application, and then mapping their “/oauth/token” to different endpoints.
Or: Sending custom data in the oauth request to know where the request is coming from, and then dynamically selecting an authentication manager.
Or: Associating different authentication manager to different OAuth clients, and then ensuring that each app will have its respective client ID.
If any of these solutions is possible, or if there is another way to accomplish this, please let me know.
Any help is appreciated.
Edit - Solution
Following the answer below, I added another client with a different client ID, check the id in the UserDetailsService and then decide which db to use. Here is the code:
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
String username = user.getUsername();
if (username.equals(OAuth2Configuration.provider_app))
// Load from provider db
else if (username.equals(OAuth2Configuration.consumer_app))
// Load from consumer db
else
throw new UsernameNotFoundException("ClientID " + username + " not found.");
}
};
}
UsernamePasswordAuthenticationToken is used as /oauth/token is protected with Basic Oauth using the client id and secret.
I think you should be able to look inside SecurityContextHolder.getContext().getAuthentication.
This should be an instance of OAuth2Authentication, from which you can (after you cast) call getOAuth2Request() to get the original Oauth2Request details.
With this information you can have a single UserDetailsService that can delegate lookups to the correct db tables. You could use scopes or resourceIds to help determine what db table to use.
You could use the third option. but this is not a good principal to follow. you can send a custom param in the oauth/token end point. it can be accessed by AutoWiring HttpServletRequest in the userDetailsService.
UserDetailsService
#Autowired
private HttpServletRequest httpServletRequest;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
String userType = httpServletRequest.getParameter("user_type");
LOGGER.info("Load user method \n Username : " + username + "\nuser_type : " + userType);
if (userType == null) {
throw new CustomOauthException("User type is required !");
}
if (userType.equals(String.valueOf(MOBILE_USER))) {
//get user..
} else if (userType.equals(String.valueOf(DRIVER))) {
//get driver..
} else if (userType.equals(String.valueOf(ADMIN))) {
//get admin
}
throw new CustomOauthException("User type is not valid !");
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("Exception : " + e.getMessage());
throw new CustomOauthException(e.getMessage());
}
}
I am developing a MVC 5 application with custom role provider, but it seems that the AuthorizeAttribute never call my customer role provider, my code is as below:
My Customer provider:
namespace MyDomain
{
public class CustomRoleProvider : RoleProvider
{
public override string[] GetRolesForUser(string username)
{
using (MyContext objContext = new MyContext())
{
var objUser = objContext.Users.FirstOrDefault(x => x.Username == username);
if (objUser == null)
{
return null;
}
else
{
string[] ret = { objUser.Access_Levels.Name };
return ret;
}
}
}
public override bool IsUserInRole(string username, string roleName)
{
var userRoles = GetRolesForUser(username);
return userRoles.Contains(roleName);
}
}
My controller:
[Authorize(Roles = "Administrator")]
public class AdminController : Controller
And Web.Config:
<system.web>
<roleManager defaultProvider="CustomRoleProvider" enabled="true" >
<providers>
<clear />
<add name="CustomRoleProvider" type="Online_Storage_Portal.CustomRoleProvider" cacheTimeoutInMinutes="30"/>
</providers>
</roleManager>
</system.web>
Also my custom role provider is in the same project as my other controllers, I am able to call my custom role provider method with following code within my controller
String[] roles = Roles.GetRolesForUser(username)
but the controller with [Authorize(Roles = "Administrator")] always redirect the page to login screen even the user login and role are both valued.
Please help!!
I believe I've found the source of your problem. I'm going to assume you're using Windows Authentication, and trying to use your custom role provider in place of the Windows Groups that are automatically loaded. Looking into the MVC AuthorizeAttribute source, you'll find that it is actually calling Principal.IsInRole. Per MSDN:
InRole first checks the IsRoleListCached property to determine whether a cached list of role names for the current user is available. If the IsRoleListCached property is true, the cached list is checked for the specified role. If the IsInRole method finds the specified role in the cached list, it returns true.
If IsInRole does not find the specified role, it calls the GetRolesForUser method of the default Provider instance to determine whether the user name is associated with a role from the data source for the configured ApplicationName value.
So I'm guessing that because the Principal is a Windows Principal, it is coming in with it's roles populated and cached. When the IsInRole is called, it says 'Hey, I've already got roles, why would I go back to the provider to get them again?"
What you could do instead would be somthing like this:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
WindowsIdentity identity = HttpContext.Current.Request.LogonUserIdentity;
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new GenericIdentity(identity.Name), Roles.GetRolesForUser());
}
This will pull the windows identity off the HttpContext, use the name to explicitly fetch roles from your custom provider, and then slap a new GenericPrincipal on the request instead. I went further and implemented some logic to store the roles in an encrypted cookie so we don't have to go the role provider on each request.
void Application_PostAuthenticateRequest()
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket authTicket;
if (authCookie == null || authCookie.Value == "")
{
string[] getRoles = Roles.GetRolesForUser();
authTicket = new FormsAuthenticationTicket(1,
User.Identity.Name,
DateTime.Now,
DateTime.Now.AddMinutes(20),
true,
String.Join(";", getRoles));
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Current.Response.Cookies.Add(authCookie);
}
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
string[] roles = authTicket.UserData.Split(';');
if (Context.User != null)
Context.User = new System.Security.Principal.GenericPrincipal(Context.User.Identity, roles);
}
I have implemented change password functionality using spring security but ((UserDetails) principal).getPassword()) is returning null for logged in user.
If I remember correctly, this used to work earlier in 3.0. Was this changed in 3.1 so that the current password of the logged in user cannot be retrieved?
In the below code I am checking the current password typed in user password from the web page. I am then checking if the logged in user's password matches the typed in password. If it does then I want to set oldPasswordMatchNewPassword = true.
How do I implement this functionality?
#RequestMapping(value = "/account/changePassword.do", method = RequestMethod.POST)
public String submitChangePasswordPage(
#RequestParam("oldpassword") String oldPassword,
#RequestParam("password") String newPassword) {
Object principal = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
String username = principal.toString();
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
System.out.println("username: " + username);
System.out.println("password: "
+ ((UserDetails) principal).getPassword());
if (((UserDetails) principal).getPassword() != null) {
if (((UserDetails) principal).getPassword().equals(oldPassword)) {
oldPasswordMatchNewPassword = true;
}
}
}
if (oldPasswordMatchNewPassword == true) {
logger.info("Old password matches new password. Password will be updated.");
changePasswordDao.changePassword(username, newPassword);
SecurityContextHolder.clearContext();
return "redirect:home.do";
} else {
logger.info("Old password did not match new password. Password will be not be updated.");
return null;
}
}
I put a couple of sysout()s so that I can see the values returned.
For ((UserDetails) principal).getUsername() I can see the correct logged in user.
((UserDetails) principal).getPassword() it is returning null.
How do I get ((UserDetails) principal).getPassword() this value?
Thanks in advance!
I used this block of code (erase-credentials="false") to fix this. I do not know if this is an elegant solution but it fixed my problem:
<authentication-manager alias="authenticationManager" erase-credentials="false">
<!-- authentication-provider user-service-ref="userService" -->
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource" />
</authentication-provider>
</authentication-manager>
Yes, this has changed in version 3.1. Credentials are cleared after a successfull authentication by default. You can set eraseCredentialsAfterAuthentication to false on the ProviderManager to prevent this.
See details here: http://static.springsource.org/spring-security/site/docs/3.2.x/reference/core-services.html#core-services-erasing-credentials
Since the password isn't retained in memory after the user has been authenticated (generally a good thing), you would need to explicitly reload it in order to use it. An alternative and more flexible strategy is to inject an instance of the AuthenticationManager and use that directly:
String name = SecurityContextHolder.getContext().getAuthentication();
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(name, oldPassword));
// Update password here with your dao
} catch (AuthenticationException e) {
// Old password was wrong
}
that way you don't need to worry about things like password-encoding strategies. Note that you shouldn't be storing passwords in plain text. They should be hashed using bcrypt or something similar.