Apache Shiro: not able to access secured URL although logged in successfully - java

Im currenlty working on a Java EE Web application with Spring MVC.
I want to secure it with Apache Shiro and the form-based authentication with the
FormAuthenticationFilter is already working.
Now i want to customize the login and write my own login-controller.
Here is my Problem: the authentication works, I don't get any exceptions, but I am still unable to acess the secured URL (/welcome.jsp), even if I try to access it directly, although I am logged in correctly (Subject.isAuthenticated returns true) .
Here is my shiro.ini:
# =======================
# Shiro INI configuration
# =======================
[main]
authc = org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter
#authc = org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authc.loginUrl = /login.jsp
authc.successUrl = /welcome.jsp
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
[users]
admin=admin, ROLE_ADMIN
[roles]
ROLE_ADMIN = *
[urls]
/login.jsp = authc
/welcome.jsp = authc
And here is the method in the respective controller class:
#RequestMapping(value="/login", params ={"username","password"})
public ModelAndView login(HttpServletRequest request, HttpServletResponse response,
#RequestParam("username") String username, #RequestParam("password") String password) {
if (!this.currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
this.currentUser.login(token);
} catch (UnknownAccountException uae) {
logger.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
logger.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
logger.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
catch (AuthenticationException ae) {
//TODO
}
logger.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
}
String message = "";
ModelAndView modelAndView = new ModelAndView("login", "message", message);
String fallbackUrl = "/welcome.jsp";
try
{
WebUtils.redirectToSavedRequest(request, response, fallbackUrl);
}
catch (IOException e)
{
e.printStackTrace();
}
return modelAndView;
}
I would be very happy, if someone could tell me, what I'm doing wrong, I am stuck at this for a few days now and haven't found anything on the web.
With best regards,
M.B.
P.S. I haven't posted my web.xml, and my pom.xml because shiro is already working with the FormAuthenticationFilter. But if someone still needs these files to give me an answer, it is no problem, to post them too.
UPDATE 1: It has to be an issue with Shiro itself. If I escape only the last line in the shiro.ini file,
#/welcome.jsp = authc
the redirection works correctly and the username: "admin" is only shown on the welcome-page, if I enter the username and password correctly.
For some reason, shiro doesn't accept the authenticated user but I have absolutely no idea why.

I finally found a solution, I hope, this will help somebody else.
The problem was that I created a field for the currentUser (as you can see in my method) and I initialized it in the constructor of the Controller. At that time, no security manager was found, so I created a SecurityManager instance too, but now this was a different one, than apache shiro used for redirecting me.
To solve this, i simply had to instanciate
this.currentUser = SecurityUtils.getSubject();
in the respective method and not in the constructor.

Related

HttpServletRequest userprincipal is null at first load

im using javax.servlet.http.HttpServletRequest; to get the request in java controller, i just want to create controller that returns actual user name, im using method getUserPrincipal() in order to get the user in the actual session, it doesn't work at the first page load (returns null) but when i reload the page it works perfectly.
Im using
spring framework 5.3.9
javaee-api 8.0
weblogic 14.1.1
vuejs 3.2.37
Here is my java controller
#GetMapping("/username")
public ResponseEntity<?> getname(HttpServletRequest request) {
Map<String, String> response = new HashMap<String, String>();
String username = "";
try {
Principal p = request.getUserPrincipal();
if (p == null || p.getName() == null || p.getName().equals("")) {
username = "undefined";
} else {
username = p.getName();
}
response.put("username", username);
return new ResponseEntity<Object>(
response, HttpStatus.OK);
} catch (Exception ex) {
System.out.println(ex.getMessage());
response.put("username", "");
return new ResponseEntity<Object>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I have tried something a little tricky, reload the page in my Vue file when username is undefined, it works but i need this to work at first load.
Can you help me with this issue?
Thanks
Is it possible the first request is sent without user authentication?
That's what browsers would typically do, and only when the server responds with HTTP status 403 they would try again with credentials.
To not deal with such fuss in your application you could simply set a security constraint that only allows authenticated users to access. The container will then automatically send the 403 response and your application would only see valid traffic.

How to authenticate and redirect to external application for SSO?

I am trying to implement a simple SSO feature in my spring-based web application. Scenario:
I have a main application Application1 and a secondary Application2. Both have their own login mechanisms (using spring-security login-forms) in place.
I want to implement SSO in Application1, so that when user logs-in to Application1, he can also seamlessly access Application2 via a link without having to fill up login details for Application2.
Here's what I have tried:
I created an API in Application2 which takes email as input, validates it, creates user session, and returns a url string.
#RequestMapping(path = "/sso/login", consumes = "application/json", method = RequestMethod.POST)
public String login(#RequestBody SSOparams params, HttpServletRequest req, ModelMap model) {
// 1. validates email from params
// 2. creates Authentication object:
UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(username, password);
Authentication auth = authManager.authenticate(authReq);
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
HttpSession session = req.getSession(true);
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sc);
// 3. returns a url string:
return "/user/dashboard";
}
User logs-in to Application1.
Inside Application1's home-page, when user clicks on a Application2's link, a call is made to Application1's controller method.
Application1's controller method calls Application2's login API with an email parameter, and finally redirects to the url returned from the API.
Application1's controller method:
#RequestMapping(value = "/callapplication2", method = RequestMethod.POST)
public String callapplication2(ModelMap model,HttpSession session) {
String output = "";
String redirectionUrl = "";
try {
// 1. calling application2's login API
URL url = new URL("http://localhost:8080/application2/api/sso/login");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
String input = "{\"uniqueemail\":\"abc#gmail.com\"}";
OutputStream os = conn.getOutputStream();
os.write(input.getBytes());
os.flush();
BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));
while ((output = br.readLine()) != null) {
redirectionUrl = redirectionUrl + output;
}
conn.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 2. returns a url
return "redirect:http://localhost:8080/application2" + redirectionUrl ;
}
All of the above is working fine. But when I login to Application1 and click on application2's link, I expect application should redirect to
http://localhost:8080/application2/user/dashboard
without having to fill up credentials.
But instead, Application2's login page opens. I can see in the network console of Chrome that /user/dashboard is being called, but since the page is secured, I am redirected to application2's login page.
Does this mean that the authentication I created using API is not being used. What am I missing?
Best approach is to set filter in web.xml and put it in top of the list.
Whenever your application will get any request it will first go to the filter and there you will check that session is present or not if its null then simply redirect to your sso login page else respective landing page.
Now in your case,
Solution i can see
1) Put filter into app2 web.xml
2) Now when you redirect from app1 to app2 (Pass one parameter anything like username, email whatever)
3) Store it into the session.
4) Whenever any request will come to app2 you will first verify session from filter, If username found that means user not need to login again else redirect to sso login page.
Thats standars steps (I belive)
5) Having a peek into your implementation.Specifically you have to add one more step into app filter. When you are redirecting from app1 for te first time with http://localhost:8080/application2/user/dashboard ( You will pass one parameter along with this url as explained above).
This let you to check 2 condition into your filter. Either request should have valid parameter or username should be into session. If any condition stated true you can let redirect request to further else you have to redirect to login page.
Hope this will help to resolve your issue.

JHipster Using LDAP Authentification

I've started to use JHipster weeks ago and everything went find since now. I want to have a LDAP authentification with at the same time the default authentification of JHipster.
I followed this https://jhipster.github.io/tips/016_tip_ldap_authentication.html and it doesn't work as planned.
Actually my configuration is connecting well to my LDAP server and i know by viewing logs that the login search into the LDAP server and compare the password.
The problem is the login fail with the error :
UT005023: Exception handling request to /api/authentication
org.springframework.security.core.userdetails.UsernameNotFoundException: User nseys was not found in the database
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.lambda$onLoginSuccess$1(PersistentTokenRememberMeServices.java:116)
at java.util.Optional.orElseThrow(Optional.java:290)
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.onLoginSuccess(PersistentTokenRememberMeServices.java:116)
at org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.loginSuccess(AbstractRememberMeServices.java:294)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
...
The thing is I want JHipster to automatically create the user in database when it doesn't exist in there with a mapping of parameters (but only when it's a LDAP user) and just connect if it's already done.
I've searched Spring-security solution aswell but the implementations are too far away from the initial files created by JHipster and I don't want to destroy all this.
Well I tried something that work, I don't know if this is how I should have done, but since I've found nothing about that, and it's not documented alot, I'll stick with that solution unless I find a better solution.
// PersistentTokenRememberMeServices.java
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication
successfulAuthentication) {
String login = successfulAuthentication.getName();
log.debug("Creating new persistent login for user {}", login);
PersistentToken t = new PersistentToken();
t.setSeries(RandomUtil.generateSeriesData());
t.setTokenValue(RandomUtil.generateTokenData());
t.setTokenDate(LocalDate.now());
t.setIpAddress(request.getRemoteAddr());
t.setUserAgent(request.getHeader("User-Agent"));
PersistentToken token = userRepository.findOneByLogin(login).map(u -> {
t.setUser(u);
return t;
}).orElse(null);
if (token == null) {
if (successfulAuthentication.getPrincipal() instanceof LdapUserDetails) {
User ldapUser = new User();
ldapUser.setLogin(login);
ldapUser.setPassword(RandomStringUtils.random(60)); // We use LDAP password, but the password need to be set
ldapUser.setActivated(true);
CustomLdapUserDetails customLdapUserDetails = (CustomLdapUserDetails) successfulAuthentication.getPrincipal();
ldapUser.setEmail(customLdapUserDetails.getEmail());
ldapUser.setFirstName(customLdapUserDetails.getFirstName());
ldapUser.setLastName(customLdapUserDetails.getLastName());
Set<Authority> authorities = new HashSet<>();
authorities.add(this.authorityRepository.findOneByName("ROLE_USER"));
ldapUser.setAuthorities(authorities);
ldapUser.setLangKey("fr");
userRepository.save(ldapUser);
t.setUser(ldapUser);
token = t;
} else {
throw new UsernameNotFoundException("User " + login + " was not found in the database");
}
}
...
}
And I added a contextMapper to get the attributes in the LDAP server
// SecurityConfiguration.java
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new LdapUserDetailsMapper() {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
UserDetails details = super.mapUserFromContext(ctx, username, authorities);
return new CustomLdapUserDetails((LdapUserDetails) details, ctx);
}
};
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(applicationProperties.getLdap().getUrl());
contextSource.setBase(applicationProperties.getLdap().getBase());
contextSource.setUserDn(applicationProperties.getLdap().getUserDn());
contextSource.setPassword(applicationProperties.getLdap().getPassword());
contextSource.afterPropertiesSet(); //needed otherwise you will have a NullPointerException in spring
auth.ldapAuthentication()
.userDetailsContextMapper(userDetailsContextMapper())
.userSearchBase(applicationProperties.getLdap().getSearchBase()) //don't add the base
.userSearchFilter(applicationProperties.getLdap().getSearchFilter())
.contextSource(contextSource)
;
}

Outlook OAuth2 access mails

I am following this post: Outlook RestGettingStarted. From my Java app I am trying to get AccessToken and RefreshToken. When I made Authorization code request, it ended into following error:
Sorry, but we’re having trouble signing you in. We received a bad
request.
Additional technical information: Correlation ID:
ed838d66-5f2e-4cfb-9223-a29082ecb26f Timestamp: 2015-08-20 10:20:09Z
AADSTS90011: The 'resource' request parameter is not supported.
NOTE: URL formation is correct as per documentation.
So, I removed "resource" query parameter from my code. And redirected authorize url in browser. On user consent I got authorization code. Using this code I got AccessToken. But when I try to connect with Outlook IMAP server it failed. Java ref Link for details: Java OAuth2
But it gives me error:
[AUTHENTICATIONFAILED] OAuth authentication failed.
NOTE: I added correct scope, and user email.
Then using obtained Access Token I made Mail Rest API call to get Messages from User Inbox. It ended into following error:
HTTP response:
{"error":{"code":"MailboxNotEnabledForRESTAPI","message":"REST API is
not yet supported for this mailbox."}}
Can anyone help me for following:
What is the exact cause for: "AADSTS90011: The 'resource' request parameter is not supported" after following Outlook dev docs.
How to resolve "MailboxNotEnabledForRESTAPI" error.
Is it possible to connect using java mail APIs to Outlook IMAP server with correct AccessToken ?
I ran into this recently, but don't remember which solved it. One main issue is in the documentation in that it is varying. It will tell you to attach "resource", but that is for something else like Azure.
Here is the code I used:
First request to send:
private static final String USER_OAUTH2_AUTHORIZE_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
public String getOAuthDialog(Http.Request request) {
return USER_OAUTH2_AUTHORIZE_URL
+ "?client_id=" + config.getClientId()
+ "&redirect_uri=" + getOutlookLoginRedirect(request)
+ "&response_type=code"
+ "&scope=https%3A%2F%2Foutlook.office.com%2Fmail.send%20" +
"https%3A%2F%2Foutlook.office.com%2Fmail.readwrite%20" +
"offline_access%20openid%20email%20profile"
+ "&state=" + crypto.generateSignedToken();
}
Scope was the hardest thing to figure out. I found a lot of ones that did not work. And it wasn't clear that I needed to separate them with spaces.
Then they will send you a request to your redirect url that was supplied. It will contain a code which you need to exchange for the data you requested in the scope. The redirect url that is supplied needs to be the exact same. Also you need to register the redirect url on your application portal under the Platform->Add Platform->Redirect URI->Add Url
private static final String USER_ACCESS_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
private Map<String, String> sendOutlookUserOAuthRequest(Http.Request request, String code) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("code", code, true);
params.put("redirect_uri", getOutlookLoginRedirect(request), true);
params.put("grant_type", "authorization_code");
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
Map<String, String> result = new HashMap<>();
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
return result;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
result.put("access_token", node.get("access_token").asText());
}
if (node.hasNonNull("refresh_token")) {
result.put("refresh_token", node.get("refresh_token").asText());
}
if (node.hasNonNull("id_token")) {
String[] tokenSplit = node.get("id_token").asText().split("\\.");
if (tokenSplit.length >= 2) {
try {
JSONObject jsonObject = new JSONObject(new String(Base64.getDecoder().decode(tokenSplit[1])));
if (jsonObject.has("name")) {
result.put("name", jsonObject.get("name").toString());
}
if (jsonObject.has("email")) {
result.put("outlookUid", jsonObject.get("email").toString());
} else if (jsonObject.has("preferred_username")) {
result.put("outlookUid", jsonObject.get("preferred_username").toString());
}
} catch (JSONException e) {
log.error("Error extracting outlookUid from id_token: ", e);
}
}
}
return result;
}
Another request that you might need is to update the refresh token:
private String getAccessTokenFromRefreshToken(User user) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("grant_type", "refresh_token");
params.put("refresh_token", user.getOutlookRefreshToken());
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
log.error("Failure to refresh outlook access token for user: " + user +
". Received status: " + wsResponse.getStatus() + " : " + wsResponse.getStatusText());
return null;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
String accessToken = node.get("access_token").asText();
return accessToken;
} else {
log.error("Outlook refresh token failure, 'access_token' not present in response body: " + wsResponse.getBody());
return null;
}
}
One issue I ran into that took far longer than I would have hoped was in getting the clientId and clientSecret. This was because the language microsoft uses wasn't the most explicit. Client Id and application id are used interchangeably. The client secret is also the password that you create on the Application Portal, not to be confused with the Private Key that you can generate.
So you actually want the application_id and the password, although they refer to them as client_id and client_secret with no direct indication as to the lines drawn.
This is all assuming you have set up an application on the Outlook Application Portal. https://apps.dev.microsoft.com/
I hope this helps, although I assume you probably already solved this.
I faced the same problem with Java mail. You need to add service principals for your application on the Azure AD.
Find complete steps explained in Medium article Complete guide: Java Mail IMAP OAuth2.0 Connect Outlook | by Ritik Sharma | Dec, 2022.

Using HTTP Request.login with JBoss/JAAS

I have successfully setup a JBoss security domain, and can authenticate using BASIC authentication (as defined in web.xml). This all works well. I cannot however figure out how to use the http request.login method.
The following security domain (from jboss-web.xml) works for BASIC authentication:
<jboss-web>
<context-root>/myapp</context-root>
<security-domain>java:/jaas/myapp-realm</security-domain>
</jboss-web>
But when I use request.login as follows:
public void login() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
request.login(username, password);
}
catch (ServletException ex) {
java.util.logging.Logger.getLogger(UserLogin.class.getName()).log(Level.SEVERE, null, ex);
}
}
I get the following exception:
javax.servlet.ServletException: Failed to authenticate a principal
I know the username/pasword is fine (it worked fine using BASIC auth). I have TRACE level logging on, and it doesn't look like it is even trying to authenticate. What have I missed?
See http://java-web-development.blogspot.com/2011/07/jee-6-security-part-two-implementation.html if you need more details about my setup/config. I am using JBoss 6.
It is now working. I made sure FORM based authentication worked, and once that worked I went back to using request.login and it worked?! I use hot deployments via JRebel so it is a possibility I had authenticated with BASIC auth and it left a user principal in my session which then caused the request.login to fail (request.login throws an exception if you are already authenticated). I swear I had done a hard restart of JBoss, but this is the only logical thing I can think of.
I now have a sanity check around the login, like so:
public void login() {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
try {
Principal userPrincipal = request.getUserPrincipal();
if (request.getUserPrincipal() != null) {
request.logout();
}
request.login(username, password);
userPrincipal = request.getUserPrincipal();
authUser = userDao.findByLogin(userPrincipal.getName());
}
catch (ServletException ex) {
java.util.logging.Logger.getLogger(UserLogin.class.getName()).log(Level.SEVERE, null, ex);
}

Categories