How to use open Id in jsp - struts2

Hi I am trying to build a login system like Stack Overflow but not find the right way to do this in JSP. I am working in struts2.

The following illustrate Single Sign On (SSO) using Oauth, for which you can create a SSO system similar to that of Stack Overflow.
Use scribe: https://github.com/fernandezpablo85/scribe-java/wiki/getting-started
The following example will demonstrate using Twitter...
1) Demonstrate an action to get twitter credentials.
package com.quaternion.struts2basic.action;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.SessionAware;
import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.TwitterApi;
import org.scribe.model.Token;
import org.scribe.oauth.OAuthService;
#Results(value = {
#Result(name = "success", location = "${authorizationURL}", type = "redirect"),
#Result(name = "error", location = "/WEB-INF/content/error.jsp")
})
public class TwitterGrantAccess extends ActionSupport implements SessionAware {
private Map<String, Object> session;
private String authorizationURL = null;
#Override
public String execute() {
//Twitter twitter = new TwitterFactory().getInstance();
String consumer_key = "rUPV8tpIcFtyMeSDlnzclA";
String consumer_secret = "16omdjNoEYgwoXfZMc0XrXSxiHDaS0UZUxQzWhTFg";
OAuthService twitterService = new ServiceBuilder()
.provider(TwitterApi.class)
.apiKey(consumer_key)
.apiSecret(consumer_secret)
.callback("http://127.0.0.1:8080/Struts2Basic/twitter-callback")
.build();
Token requestToken = twitterService.getRequestToken();
authorizationURL = twitterService.getAuthorizationUrl(requestToken);
session.put("twitterService", twitterService);
session.put("requestToken", requestToken);
return SUCCESS;
}
public String getAuthorizationURL() {
return this.authorizationURL;
}
#Override
public void setSession(Map<String, Object> map) {
this.session = map;
}
}
2) Action which twitter redirects back to...
package com.quaternion.struts2basic.action;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.SessionAware;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
#Results(value = {
#Result(name = "success", location = "/WEB-INF/content/twitter-callback-success.jsp"),
#Result(name = "error", location = "/WEB-INF/content/error.jsp")
})
public class TwitterCallback extends ActionSupport implements SessionAware {
private Map<String, Object> session;
private String key;
private String secret;
//returned from twitter
private String oauth_token;
private String oauth_verifier;
#Override
public String execute() {
if (session.containsKey("accessToken") && session.get("accessToken") != null) {
return SUCCESS; //accessToken already exists!
}
Token requestToken = (Token) session.get("requestToken");
if (requestToken == null) {
super.addActionError("requestToken is null");
return ERROR;
}
OAuthService twitterService = (OAuthService) session.get("twitterService");
System.out.println(requestToken.toString());
System.out.println(this.getOauth_verifier());
//Token accessToken = twitter.getOAuthAccessToken(requestToken, this.getOauth_verifier());
Token accessToken = twitterService.getAccessToken(requestToken, new Verifier(this.getOauth_verifier()));
session.put("accessToken", accessToken);
this.setKey(accessToken.getToken()); //just to see something happen
this.setSecret(accessToken.getSecret());//just to see something happen
return SUCCESS;
}
#Override
public void setSession(Map<String, Object> map) {
this.session = map;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getOauth_token() {
return oauth_token;
}
public void setOauth_token(String oauth_token) {
this.oauth_token = oauth_token;
}
public String getOauth_verifier() {
return oauth_verifier;
}
public void setOauth_verifier(String oauth_verifier) {
this.oauth_verifier = oauth_verifier;
}
}
I'll omit the views because they really don't do anything
3) An action which writes "Hello from Struts2!", which isn't very good because twitter will only let you run this once and because the status is the same will not let you post it again... but it gets the process across. After updating the status it redirects to your twitter page, if you change the "YOUR_USER_NAME" part of the url in the redirect of course.
package com.quaternion.struts2basic.action;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.SessionAware;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
#Results({
#Result(name = "success", location = "https://twitter.com/#!/YOUR_USER_NAME", type = "redirect")
})
public class Tweet extends ActionSupport implements SessionAware {
private Map<String, Object> session;
private String status;
#Override
public String execute() {
Token accessToken = (Token) session.get("accessToken");
OAuthService twitterService = (OAuthService) session.get("twitterService");
String url = "http://api.twitter.com/1/statuses/update.json?status=";
String twitterStatus = "hello!";
OAuthRequest request = new OAuthRequest(Verb.POST, url + twitterStatus);
twitterService.signRequest(accessToken, request);
Response response = request.send();
return SUCCESS;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatus() {
return this.status;
}
#Override
public void setSession(Map<String, Object> map) {
session = map;
}
}
That is pretty much it. The nice things about scribe is it is so easy to configure for the different providers (for basic authentication, using their APIs after is another matter and that is up to you).

It dependents upon how you want to build it.There are certain number of library which you can use to build you login system and few of them are
Joid
openid4java
Here is a outline what all you have to do in order to make the complete flow
Create a JSP page where use can select a way to choose his login system.
Call an action class which Create an authentication request for this identifier.
Redirect user to the OpenId service provider and let him authorize themself.
Receive the OpenID Provider's authentication response at your callback action.
parse the response if you need to store some information.

Related

WebSecurityConfigurerAdapter is not being called

I am working through several tutorials to implement my own security within my project.
The problem is, as configured the system.out.println calls within the class the extends WebSecurityConfigurerAdapter are not being hit. Which means the security class is not being called at all. There are no error messages and I'm also able to navigate to any page within the site without the authorization redirecting to the login page. Also, the login page just does a post and takes me to the home page of the site.
Here is the custom web security configurer adapter:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
ShopmeUserDetailsService shopmeUserDetailsService;
#Bean
public UserDetailsService userDetailsService() {
return new ShopmeUserDetailsService();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
public DaoAuthenticationProvider authenicationProvider() {
System.out.println("In Dao auth security");
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("In configure security");
//auth.authenticationProvider(authenicationProvider());
//auth.userDetailsService(shopmeUserDetailsService);
auth
.inMemoryAuthentication()
.withUser("user1")
.password(passwordEncoder().encode("user1Pass"))
.roles("USER")
.and()
.withUser("user2")
.password(passwordEncoder().encode("user2Pass"))
.roles("USER")
.and()
.withUser("admin")
.password(passwordEncoder().encode("adminPass"))
.roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("In configure security auth");
http
.authorizeRequests()
.anyRequest().authenticated() //all URLs are allowed by any authenticated user, no role restrictions.
.and()
.formLogin() //enable form based authentication
.loginPage("/login") //use a custom login URI
.usernameParameter("email")
.permitAll(true) //login URI can be accessed by anyone
.and()
.logout() //default logout handling
.permitAll(); //allow all as it will be accessed when user is not logged in anymore
}
#Override
public void configure(WebSecurity web) throws Exception{
System.out.println("In configure ignorings");
web.ignoring().antMatchers("/images/**", "/js/**", "/webjars/**" );
}
}
Here is the main app class:
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
#EntityScan({"com.shopme.common.entity", "com.shopme.admin.user"})
public class ShopmeBackendApplication {
public static void main(String[] args) {
SpringApplication.run(ShopmeBackendApplication.class, args);
}
}
My main controller:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
#Controller
public class MainController {
#GetMapping("")
public String viewHomePage() {
return "index";
}
#GetMapping("/login")
public String viewLoginPage() {
System.out.println("In viewLoginPage method - MainController");
return "login";
}
#PostMapping("/login")
public String login() {
System.out.println("login attempt");
return "index";
}
}
And finally my other controller for the admin pages:
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.shopme.admin.FileUploadUtil;
import com.shopme.common.entity.Role;
import com.shopme.common.entity.User;
#Controller
public class UserController {
#Autowired
private UserService userService;
//private final java.nio.file.Path root = Paths.get("user_photos");
//Updated method to list the first page of users
#GetMapping("/users")
public String listFirstPage(Model model) {
return listUsersByPage(1, model, null);
}
#GetMapping("/users/new")
public String newUser(Model model) throws IOException {
System.out.println("new User method");
List<Role> roles = userService.listRoles();
//System.out.println(multiPartFile.getOriginalFilename());
//String fileName =
StringUtils.cleanPath(multiPartFile.getOriginalFilename());
//String uploadDir = "user_photos";
//FileUploadUtil.saveFile(uploadDir, fileName, multiPartFile);
//Files.copy(multiPartFile.getInputStream(), ((java.nio.file.Path)
this.root).resolve(multiPartFile.getOriginalFilename()));
User user = new User();
user.setEnabled(true);
model.addAttribute("user", user);
model.addAttribute("roles", roles);
model.addAttribute("pageTitle", "Create New User");
return "user_form";
}
#PostMapping("/users/save")
public String saveUser(User user, RedirectAttributes redirect, #RequestParam("image") MultipartFile multiPartFile) throws IOException {
System.out.println(user);
System.out.println(multiPartFile.getOriginalFilename());
String fileName = StringUtils.cleanPath(multiPartFile.getOriginalFilename());
String uploadDir = "user_photos";
FileUploadUtil.saveFile(uploadDir, fileName, multiPartFile);
//Files.copy(multiPartFile.getInputStream(), ((java.nio.file.Path) this.root).resolve(multiPartFile.getOriginalFilename()));
userService.save(user);
redirect.addFlashAttribute("message", "User has been saved successfully!");
return "redirect:/users/page/1?keyword=" + user.getId();
}
#GetMapping("/users/edit/{id}")
public String editUser(#PathVariable(name = "id") Integer id, Model model, RedirectAttributes redirect){
try {
Optional<User> user = userService.getUserById(id);
List<Role> roles = userService.listRoles();
model.addAttribute("user", user);
model.addAttribute("roles", roles);
model.addAttribute("pageTitle", "Edit User (ID: " + id + ")");
return "user_form";
} catch (UserNotFoundException ex) {
redirect.addFlashAttribute("message", ex.getMessage());
return "redirect:/users";
}
}
#GetMapping("users/delete/{id}")
public String deleteUser(#PathVariable(name="id") Integer id, Model model, RedirectAttributes redirect) {
userService.deleteUserById(id);
redirect.addFlashAttribute("message", "User has been deleted successfully!");
return "redirect:/users";
}
#GetMapping("/users/{id}/enabled/{status}")
public String updateUserEnabledStatus(#PathVariable("id") Integer id, #PathVariable("status") boolean enabled, RedirectAttributes redirect) {
userService.updateUserEdabledStatus(id, enabled);
String status = enabled ? "enabled" : "disabled";
String message = "THe user Id " + id + " has been " + status;
redirect.addFlashAttribute("message", message);
return "redirect:/users";
}
#GetMapping("/users/page/{pageNumber}")
public String listUsersByPage(#PathVariable(name = "pageNumber") int pageNumber, Model model, #Param("keyword") String keyword) {
Page<User> page = userService.listByPage(pageNumber, keyword);
List<User> userPagedList = page.getContent();
System.out.println("Pagenumber: " + pageNumber);
System.out.println("Total Elements: " + page.getTotalElements());
System.out.println("Totals Pages: " + page.getTotalPages());
long startCount = (pageNumber - 1) * UserService.USERS_PER_PAGE +1;
long endCount = startCount + UserService.USERS_PER_PAGE -1;
if(endCount > page.getTotalElements()){
endCount = page.getTotalElements();
}
model.addAttribute("totalPages", page.getTotalPages());
model.addAttribute("currentPage", pageNumber);
model.addAttribute("startCount", startCount);
model.addAttribute("endCount", endCount);
model.addAttribute("totalItems", page.getTotalElements());
model.addAttribute("users", userPagedList);
model.addAttribute("keyword", keyword);
return "users";
} //end listUserByPage
#GetMapping("/users/export/csv")
public void exportToCSV(HttpServletResponse response) throws IOException {
List<User> userList = userService.listAll();
UserCsvExporter exporter = new UserCsvExporter();
exporter.export(userList, response);
} //end exportToCsv
#GetMapping("/users/export/excel")
public void exportToExcel(HttpServletResponse response) throws IOException {
List<User> userList = userService.listAll();
UserExcelExporter exporter = new UserExcelExporter();
exporter.export(userList, response);
} //end exportToExcel
#GetMapping("/users/export/pdf")
public void exportToPdf(HttpServletResponse response) throws IOException {
List<User> userList = userService.listAll();
UserPdfExporter exporter = new UserPdfExporter();
exporter.export(userList, response);
} //end exportToPdf
} //end of class
I've spent two days investigating this with no results... any help would greatly be appreciated.
I figured out why this was not working, well at least I have it working with this solution.
I included the WebSecurityConfig.class in the start-up class of the project as shown below:
#SpringBootApplication
#EntityScan({"com.shopme.common.entity", "com.shopme.admin.user"})
public class ShopmeBackendApplication {
public static void main(String[] args) {
SpringApplication.run(new Class[]
{ShopmeBackendApplication.class,
WebSecurityConfig.class}, args);
}
}

How to set user authorities from user claims return by an oauth server in spring security

I recently wrote a spring boot project that uses spring security oauth2, the auth server is IdentityServer4 for some reason, I can successfully login and get username in my project but I cannot find any way to set user's authority/role.
request.isUserInRole always return false.
#PreAuthorize("hasRole('rolename')") always lead me to 403.
Where can I place some code to set the authorities?
The server has returned some user claims through userinfo endpoint, and my project received them, and I can even see it in the principle param of my controller.
This method always return 403
#ResponseBody
#RequestMapping("admin")
#PreAuthorize("hasRole('admin')")
public String admin(HttpServletRequest request){
return "welcome, you are admin!" + request.isUserInRole("ROLE_admin");
}
application.properties
spring.security.oauth2.client.provider.test.issuer-uri = http://localhost:5000
spring.security.oauth2.client.provider.test.user-name-attribute = name
spring.security.oauth2.client.registration.test.client-id = java
spring.security.oauth2.client.registration.test.client-secret = secret
spring.security.oauth2.client.registration.test.authorization-grant-type = authorization_code
spring.security.oauth2.client.registration.test.scope = openid profile
I print the claims
#ResponseBody
#RequestMapping()
public Object index(Principal user){
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken)user;
return token.getPrincipal().getAttributes();
}
and get the result show there is a claim named 'role'
{"key":"value","role":"admin","preferred_username":"bob"}
Anybody can help me and give me a solution please?
EDIT 1:
The reason is oauth2 client has removed the extracter, and I have to implement the userAuthoritiesMapper.
Finally I got this work by adding the following class:
#Configuration
public class AppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(this.userAuthoritiesMapper());
//.oidcUserService(this.oidcUserService());
super.configure(http);
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.containsClaim("role")){
String roleName = "ROLE_" + userInfo.getClaimAsString("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("role")){
String roleName = "ROLE_" + (String)userAttributes.get("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
}
});
return mappedAuthorities;
};
}
}
The framework changes so fast and the demos on the web is too old!
I spent a few hours and I find the solution. The problem is with spring oauth security, by default it obtain the user roles from the token using the key 'authorities'. So, I implemented a custom token converter.
The first you need is the custom user token converter, here is the class:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class CustomUserTokenConverter implements UserAuthenticationConverter {
private Collection<? extends GrantedAuthority> defaultAuthorities;
private UserDetailsService userDetailsService;
private final String AUTHORITIES = "role";
private final String USERNAME = "preferred_username";
private final String USER_IDENTIFIER = "sub";
public CustomUserTokenConverter() {
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public void setDefaultAuthorities(String[] defaultAuthorities) {
this.defaultAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.arrayToCommaDelimitedString(defaultAuthorities));
}
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
Map<String, Object> response = new LinkedHashMap();
response.put(USERNAME, authentication.getName());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USER_IDENTIFIER)) {
Object principal = map.get(USER_IDENTIFIER);
Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);
if (this.userDetailsService != null) {
UserDetails user = this.userDetailsService.loadUserByUsername((String)map.get(USER_IDENTIFIER));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
} else {
return null;
}
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
if (!map.containsKey(AUTHORITIES)) {
return this.defaultAuthorities;
} else {
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String) {
return AuthorityUtils.commaSeparatedStringToAuthorityList((String)authorities);
} else if (authorities instanceof Collection) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection)authorities));
} else {
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}
}
}
The you need a custom token converter, here is:
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.stereotype.Component;
import java.util.Map;
#Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication authentication = super.extractAuthentication(claims);
authentication.setDetails(claims);
return authentication;
}
}
And finally you ResourceServerConfiguration looks like this:
import hello.helper.CustomAccessTokenConverter;
import hello.helper.CustomUserTokenConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests()
.anyRequest().access("hasAnyAuthority('Admin')");
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("arawaks");
}
#Bean
#Primary
public RemoteTokenServices tokenServices() {
final RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId("resourceId");
tokenServices.setClientSecret("resource.secret");
tokenServices.setCheckTokenEndpointUrl("http://localhost:5001/connect/introspect");
tokenServices.setAccessTokenConverter(accessTokenConverter());
return tokenServices;
}
#Bean
public CustomAccessTokenConverter accessTokenConverter() {
final CustomAccessTokenConverter converter = new CustomAccessTokenConverter();
converter.setUserTokenConverter(new CustomUserTokenConverter());
return converter;
}
}
Apparently #wjsgzcn answer (EDIT 1) DOES NOT WORK for reasons below
If you print the attributes returned by the Oauth2UserAuthirty class you will soon notice the contents of the JSON data does not have the role key instead has an authorities key hence you need to use that key to iterate over the list of authorities (roles) to get the actual role name.
Hence the following lines of code will not work as there is no role key in the JSON data returned by the oauth2UserAuthority.getAttributes();
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("role")){
String roleName = "ROLE_" + (String)userAttributes.get("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
So instead use the following to get the actual role from the getAttributes
if (userAttributes.containsKey("authorities")){
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<Role> authorityList =
objectMapper.convertValue(userAttributes.get("authorities"), new
TypeReference<ArrayList<Role>>() {});
log.info("authList: {}", authorityList);
for(Role role: authorityList){
String roleName = "ROLE_" + role.getAuthority();
log.info("role: {}", roleName);
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
}
Where the Role is a pojo class like so
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Role {
#JsonProperty
private String authority;
}
That way you will be able to get the ROLE_ post prefix which is the actual role granted to the user after successfully authenticated to the Authorization server and the client is returned the LIST of granted authorities (roles).
Now the complete GrantedAuthoritesMapper look like the following:
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcIdToken idToken = oidcUserAuthority.getIdToken();
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
// Map the claims found in idToken and/or userInfo
// to one or more GrantedAuthority's and add it to mappedAuthorities
if (userInfo.containsClaim("authorities")){
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<Role> authorityList = objectMapper.convertValue(userInfo.getClaimAsMap("authorities"), new TypeReference<ArrayList<Role>>() {});
log.info("authList: {}", authorityList);
for(Role role: authorityList){
String roleName = "ROLE_" + role.getAuthority();
log.info("role: {}", roleName);
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
}
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
log.info("userAttributes: {}", userAttributes);
// Map the attributes found in userAttributes
// to one or more GrantedAuthority's and add it to mappedAuthorities
if (userAttributes.containsKey("authorities")){
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<Role> authorityList = objectMapper.convertValue(userAttributes.get("authorities"), new TypeReference<ArrayList<Role>>() {});
log.info("authList: {}", authorityList);
for(Role role: authorityList){
String roleName = "ROLE_" + role.getAuthority();
log.info("role: {}", roleName);
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
}
}
});
log.info("The user authorities: {}", mappedAuthorities);
return mappedAuthorities;
};
}
Now you are able to use the userAuthorityMapper in your oauth2Login as follows
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.antMatchers("/clientPage/**").hasRole("CLIENT")
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.userAuthoritiesMapper(userAuthoritiesMapper());
}

Spring Security not populating database with hashed password

I'm trying to populate the database with a hashed password and then log in to my application, by matching the data I'm submitting through my log in form, just like how a typical hashed password is suppose to work. I'm using spring security and spring boot, and so far I know that the log in form is working because I get the error Encoded password does not look like BCrypt. And I know that when I'm submitting the user to the database it's not working because I just see a plain string in the password column in the database. I'm really not sure where I'm going wrong here.
Here's my User object
package com.example.objects;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;
import com.example.security.PasswordCrypto;
import com.example.security.RoleEnum;
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "email")
private String email;
#Column(name = "termsOfService")
private Boolean termsOfService;
#OneToMany(mappedBy = "user")
private Set<UserRole> roles;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<QuestionAnswerSet> questionAnswerSet;
public static User createUser(String username, String email, String password) {
User user = new User();
user.username = username;
user.email = email;
user.password = PasswordCrypto.getInstance().encrypt(password);
if(user.roles == null) {
user.roles = new HashSet<UserRole>();
}
//create a new user with basic user privileges
user.roles.add(
new UserRole(
RoleEnum.USER.toString(),
user
));
return user;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Boolean getTermsOfService() {
return termsOfService;
}
public void setTermsOfService(Boolean termsOfService) {
this.termsOfService = termsOfService;
}
public Set<QuestionAnswerSet> getQuestionAnswerSet() {
return questionAnswerSet;
}
public void setQuestionAnswerSet(Set<QuestionAnswerSet> questionAnswerSet) {
this.questionAnswerSet = questionAnswerSet;
}
public Set<UserRole> getRoles() {
return roles;
}
public void setRoles(Set<UserRole> roles) {
this.roles = roles;
}
}
Here's my Security Config
package com.example.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static PasswordEncoder encoder;
#Autowired
private UserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.csrfTokenRepository(csrfTokenRepository());
http
.authorizeRequests()
.antMatchers("/","/home","/register", "/result").permitAll()
.anyRequest().authenticated();
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
if(encoder == null) {
encoder = new BCryptPasswordEncoder();
}
return encoder;
}
private CsrfTokenRepository csrfTokenRepository()
{
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setSessionAttributeName("_csrf");
return repository;
}
}
My user detail service
package com.example.service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.example.dao.UserDao;
import com.example.objects.UserRole;
#Service
#Qualifier("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDao;
#Transactional
#Override
public UserDetails loadUserByUsername(final String username)
throws UsernameNotFoundException {
com.example.objects.User user = userDao.findByUsername(username);
List<GrantedAuthority> authorities = buildUserAuthority(user.getRoles());
return buildUserForAuthentication(user, authorities);
}
private User buildUserForAuthentication(com.example.objects.User user,
List<GrantedAuthority> authorities) {
return new User(user.getUsername(), user.getPassword(), authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRoleName()));
}
return new ArrayList<GrantedAuthority>(setAuths);
}
}
And PasswordCrypto
package com.example.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
public class PasswordCrypto {
#Autowired
private PasswordEncoder passwordEncoder;
private static PasswordCrypto instance;
public static PasswordCrypto getInstance() {
if(instance == null) {
instance = new PasswordCrypto();
}
return instance;
}
public String encrypt(String str) {
return passwordEncoder.encode(str);
}
}
If anyone knows what I'm doing wrong and could help me out, that would be awesome, also let me know if I need to show anymore code. Thanks in advance.
Use encoder to user repository like this :
public class UserRepositoryService implements UserService {
private PasswordEncoder passwordEncoder;
private UserRepository repository;
#Autowired
public UserRepositoryService(PasswordEncoder passwordEncoder,
UserRepository repository) {
this.passwordEncoder = passwordEncoder;
this.repository = repository;
}
private boolean emailExist(String email) {
User user = repository.findByEmail(email);
if (user != null) {
return true;
}
return false;
}
private String encodePassword(RegistrationForm dto) {
String encodedPassword = null;
if (dto.isNormalRegistration()) {
encodedPassword = passwordEncoder.encode(dto.getPassword());
}
return encodedPassword;
}
#Transactional
#Override
public User registerNewUserAccount(RegistrationForm userAccountData)
throws DuplicateEmailException {
if (emailExist(userAccountData.getEmail())) {
LOGGER.debug("Email: {} exists. Throwing exception.",
userAccountData.getEmail());
throw new DuplicateEmailException("The email address: "
+ userAccountData.getEmail() + " is already in use.");
}
String encodedPassword = encodePassword(userAccountData);
User.Builder user = User.getBuilder().email(userAccountData.getEmail())
.firstName(userAccountData.getFirstName())
.lastName(userAccountData.getLastName())
.password(encodedPassword)
.background(userAccountData.getBackground())
.purpose(userAccountData.getPurpose());
if (userAccountData.isSocialSignIn()) {
user.signInProvider(userAccountData.getSignInProvider());
}
User registered = user.build();
return repository.save(registered);
}
}
For morre info, check out this repo
https://bitbucket.org/sulab/biobranch/src/992791aa706d0016de8634ebb6347a81fe952c24/src/main/java/org/scripps/branch/entity/User.java?at=default&fileviewer=file-view-default
My problem was that I needed to add user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword())); in my UserController Post method right before I saved the user

How to add twitter capability to my struts2 application

I developed struts2 web application, now I want people to login from their twitter account, so I need to place a button called "Login with Twitter" under my normal login button. I already did Facebook integration so as far as my knowledge we need to create the application first in twitter so i did that then i am confused what to do, please anyone guide me through that steps.
I don't really recall the process of registering your application with twitter but google "Single-Sign-On (SSO) using twitter".
First you need to register your application, in doing so you will be given a consumer_key and a consumer_secret (not sure if those are twitters terms).
Then the process is to send the user to twitter using those credentials, have the user sign in and then have twitter return control back to your application with a callback. That callback will contain an authorization token, which will grant access to twitter services so we will save that in the session.
After you have registered your application the following code will perform login, and let you post the string "hello!" to your twitter account (Assuming the application has this privilege).
TwitterGrantAccess.java
package com.quaternion.demo.action.twitter;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.SessionAware;
import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.TwitterApi;
import org.scribe.model.Token;
import org.scribe.oauth.OAuthService;
#Results(value = {
#Result(name = "success", location = "${authorizationURL}", type = "redirect"),
#Result(name = "error", location = "/WEB-INF/content/error.jsp")
})
public class TwitterGrantAccess extends ActionSupport implements SessionAware {
private Map<String, Object> session;
private String authorizationURL = null;
#Override
public String execute() {
//Twitter twitter = new TwitterFactory().getInstance();
String consumer_key = "NOT_PUTTING_MY_KEY_ON_STACK_OVERFLOW!";
String consumer_secret = "NOT_PUTTING_MY_CONSUMER_SECRET_ON_STACK_OVERFLOW!";
OAuthService twitterService = new ServiceBuilder()
.provider(TwitterApi.class)
.apiKey(consumer_key)
.apiSecret(consumer_secret)
.callback("http://localhost:8080/demo/twitter/twitter-callback")
.build();
Token requestToken = twitterService.getRequestToken();
authorizationURL = twitterService.getAuthorizationUrl(requestToken);
session.put("twitterService", twitterService);
session.put("requestToken", requestToken);
return SUCCESS;
}
public String getAuthorizationURL() {
return this.authorizationURL;
}
#Override
public void setSession(Map<String, Object> map) {
this.session = map;
}
}
The Callback invoked by twitter (something you will not call as a user)...
TwitterCallback.java
package com.quaternion.demo.action.twitter;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.SessionAware;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
#Results(value = {
#Result(name = "success", location = "/WEB-INF/content/twitter/twitter-callback-success.jsp"),
#Result(name = "error", location = "/WEB-INF/content/twitter/twitter-callback-error.jsp")
})
public class TwitterCallback extends ActionSupport implements SessionAware {
private Map<String, Object> session;
private String key;
private String secret;
//returned from twitter
private String oauth_token;
private String oauth_verifier;
#Override
public String execute() {
if (session.containsKey("accessToken") && session.get("accessToken") != null) {
return SUCCESS; //accessToken already exists!
}
Token requestToken = (Token) session.get("requestToken");
if (objectToken == null) {
super.addActionError("requestToken is null");
return ERROR;
}
OAuthService twitterService = (OAuthService) session.get("twitterService");
System.out.println(requestToken.toString());
System.out.println(this.getOauth_verifier());
//Token accessToken = twitter.getOAuthAccessToken(requestToken, this.getOauth_verifier());
Token accessToken = twitterService.getAccessToken(requestToken, new Verifier(this.getOauth_verifier()));
session.put("accessToken", accessToken);
this.setKey(accessToken.getToken()); //just to see something happen
this.setSecret(accessToken.getSecret());//just to see something happen
return SUCCESS;
}
#Override
public void setSession(Map<String, Object> map) {
this.session = map;
}
/**
* #return the key
*/
public String getKey() {
return key;
}
/**
* #param key the key to set
*/
public void setKey(String key) {
this.key = key;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getOauth_token() {
return oauth_token;
}
/**
* #param oauth_token the oauth_token to set
*/
public void setOauth_token(String oauth_token) {
this.oauth_token = oauth_token;
}
public String getOauth_verifier() {
return oauth_verifier;
}
public void setOauth_verifier(String oauth_verifier) {
this.oauth_verifier = oauth_verifier;
}
}
With this done your application can now use the API, let's post a tweet:
Tweet.java
//Posts the string "hello!" to the users twitter feed then redirects to
//ken_mcwilliams twitter url, because that is the account I will be
//logging into... change you your own account during development
package com.quaternion.demo.action.twitter;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.interceptor.SessionAware;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
#Results({
#Result(name = "success", location = "https://twitter.com/#!/ken_mcwilliams", type = "redirect")
})
public class Tweet extends ActionSupport implements SessionAware {
private Map<String, Object> session;
private String status;
#Override
public String execute() {
Token accessToken = (Token) session.get("accessToken");
OAuthService twitterService = (OAuthService) session.get("twitterService");
String url = "http://api.twitter.com/1/statuses/update.json?status=";
String twitterStatus;
if (status == null || status.isEmpty() == true) {
twitterStatus = "hello!";
}else{
twitterStatus = status;
}
OAuthRequest request = new OAuthRequest(Verb.POST, url + twitterStatus);
twitterService.signRequest(accessToken, request); // the access token from step 4
Response response = request.send();
return SUCCESS;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatus() {
return this.status;
}
#Override
public void setSession(Map<String, Object> map) {
session = map;
}
}
PS: twitter-callback-success.jsp and twitter-callback-error.jsp don't contain anything interesting, they just state "You now have access to twitter and everything is great!" and "Something went horribly wrong".

Struts2 request as null

Very strange error I have, I am getting request as null when I try to access it. I always used the same method to get it, but now I am having this error.
My Action look like this:
package com.deveto.struts.actions;
import com.deveto.hibernate.mappings.Slider;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.util.ServletContextAware;
/**
*
* #author denis
*/
public class ContentAction extends ActionSupport implements ServletContextAware {
HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST);
HttpServletResponse response = (HttpServletResponse) ActionContext.getContext().get(ServletActionContext.HTTP_RESPONSE);
ActionContext ac = ActionContext.getContext();
ServletContext sc = (ServletContext) ac.get(ServletActionContext.SERVLET_CONTEXT);
#Override
public String execute() throws Exception {
System.out.println("request: " + request);
return SUCCESS;
}
public ActionContext getAc() {
return ac;
}
public void setAc(ActionContext ac) {
this.ac = ac;
}
public HttpServletRequest getRequest() {
return request;
}
public void setRequest(HttpServletRequest request) {
this.request = request;
}
public HttpServletResponse getResponse() {
return response;
}
public void setResponse(HttpServletResponse response) {
this.response = response;
}
public ServletContext getSc() {
return sc;
}
public void setSc(ServletContext sc) {
this.sc = sc;
}
public void setServletContext(ServletContext sc) {
this.sc = sc;
}
}
And now I can't do nothing, the request is always null
request: null
Implement the ServletRequestAware Interface and set your request variable there instead of doing that during construction.
But normally you don't need access to the request as the params interceptor of struts does all the work the request object is needed for.
From the documentation of the ServletRequestAware-Interface:
All Actions that want to have access to the servlet request object must implement this interface.
This interface is only relevant if the Action is used in a servlet environment.
Note that using this interface makes the Action tied to a servlet environment, so it should be avoided if possible since things like unit testing will become more difficult.

Resources