I have 2 microservices. An API gateway with a JWT authentication and a microservice that manages templates from the respective user. My goal is that the user can only see his templates. But I am not clear how I do it. Do I have to generate an additional ID token or can I also solve the problem with feign?
#GetMapping("/templates/{name}/{template_id}")
public Template retrieveTemplate(#PathVariable("name") String name,#PathVariable("template_id") int template_id)
{
return templateRepository.findByTemplateIdAndName(template_id, name);
}
This is my get method to be able to display the respective template. However, I don't want the user's name to be required in REST.After the authentication it should be possible to use the token to be able to output user-specific data or? I also have two database tables. One for authentication and one for templates. In the authentication table, I once have the data username and password as well as a name of the user. In the template table I only have the name of the user (to keep the table small). This means that in the case of a get request from templates, the name of the user must be requested from the authentication microservice in order to be able to display only the templates of the user. Can someone tell me which technology I work best with?
#Service
public class JwtUtil {
private String SECRET_KEY = "helloworld";
public String extractUsername(String token)
{
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token)
{
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims,T> claimsResolver)
{
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token)
{
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token)
{
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails)
{
Map<String,Object> claims =new HashMap<>();
return createToken(claims,userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject)
{
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000*60*60*24*360))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails)
{
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
package sendMessage.LoginJwtAPIGateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter{
#Autowired
private MyUserDetailsService myUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(myUserDetailsService);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/authenticate").permitAll().
antMatchers("/users").hasRole("ADMIN")
.anyRequest().authenticated()
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
}
#Component
public class JwtRequestFilter extends OncePerRequestFilter{
#Autowired
private MyUserDetailsService userDetailsService;
#Autowired
private JwtUtil jwtUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader =request.getHeader("Authorization");
String username = null;
String jwt = null;
if(authorizationHeader !=null && authorizationHeader.startsWith("Bearer "))
{
jwt = authorizationHeader.substring(7);
username= jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication()==null)
{
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails))
{
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request,response);
}
}
package sendMessage.LoginJwtAPIGateway;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class MyUserDetails implements UserDetails{
private String username;
private String password;
private boolean active;
private List<GrantedAuthority> authorities;
public MyUserDetails()
{
}
public MyUserDetails(User user) {
this.username=user.getUsername();
this.password=user.getPassword();
this.active=user.isActive();
this.authorities =Arrays.stream(user.getRoles().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return authorities;
}
#Override
public String getPassword() {
// TODO Auto-generated method stub
return password;
}
public String getUsername() {
// TODO Auto-generated method stub
return username;
}
#Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return active;
}
}
If you're using Spring and JWT for generating tokens, then you can add token enhancers to your JWT, token enhancers allow you to add additional information to your token.
Then you can add the username or user id to your token.
In your call to your template endpoint you can remove the name part of your url. and rely on the received token to get the username.
You can find here how to add token enhancers https://www.baeldung.com/spring-security-oauth-jwt
You want to fetch some user data from his JWT token, so you need to add some extra information to user token when you're generating it, for this purpose you must use a custom TokenEnhancer.
This post might be useful for your scenario :
Can-i-include-user-information-while-issuing-an-access-token
In summary
Create CustomTokenEnhancer
Add CustomTokenEnhancer to AuthorizationServerConfigurerAdapter as a bean
Get saved data from token and use it (In the controller ,...)
Related
I have a Spring Boot MVC application which uses LDAP for authentication. This works fine, but now I have to match authenticated user (from LDAP repository)
with users from my database. I created LDAPUser:
import java.util.jar.Attributes.Name;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
#Entry(
base="ou=users",
objectClasses = { "person", "inetOrgPerson", "top" })
public class LDAPUser {
#Id
private Name id;
private #Attribute(name = "cn") String username;
private #Attribute(name = "sn") String password;
private boolean rememberme;
}
And LDAPUserRepository:
import org.springframework.data.ldap.repository.LdapRepository;
import org.springframework.stereotype.Repository;
import com.licensewatcher.model.LDAPUser;
#Repository("ldapUserRespository")
public interface LDAPUserRepository extends LdapRepository<LDAPUser>{
LDAPUser findByUsername(String username);
LDAPUser findByUsernameAndPassword(String username, String password);
/*List<LDAPUser> findByUsernameLikeIgnoreCase(String username);*/
}
and AuthUserService:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.licensewatcher.repository.LDAPUserRepository;
#Service("authUserService")
public class AuthUserService {
#Autowired LDAPUserRepository ldapUserRespository;
public boolean authenticate(LDAPUser ldapUser) {
//TODO: implement this!!!
return false;
}
public boolean authorize(LDAPUser ldapUser) {
//TODO: implement this!!!
return false;
}
}
Class WebSecurityConfig (extends WebSecurityConfigurerAdapte) configures application to submit login controller action:
#PostMapping("/login/check")
public String login(Model model, LDAPUser ldapUser, RedirectAttributes redirectAttr) {
//TODO: call authUserService.authenticate(LDAPUser ldapUser);
return "redirect:/login";
}
I want to implement authUserService.authenticate(LDAPUser ldapUser) to check LDAPUserRepository first and if user exists, check up the User from my database. If they match, add user to a session and redirect to the requested page.
Is this a good approach? Do you have any suggestions how this could be implemented in a more elegant way?
Thanks in advance!
Here is my answer based on the link I provided in the comment.
This work using the latest Spring boot version 2.7.1 and Spring security 5.7.2
I'm using Custom Spring Authentication Provider.
class CustomAuthProvider
#Component
public class CustomAuthProvider implements AuthenticationProvider {
#Autowired
RfaUserService rfaUserService;
#Autowired
AuthenticationManager ldapAuthenticationManager;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Optional<RfaUser> rfaUser = rfaUserService.findByUsername((String) authentication.getPrincipal());
if (rfaUser.isPresent()) {
return ldapAuthenticationManager.authenticate(authentication);
}
return null;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
class WebSecurityConfig
#Configuration
public class WebSecurityConfig {
#Autowired
private CustomAuthProvider customAuthProvider;
#Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.cors()
.and()
.csrf()
.disable().logout();
// here is the important part
httpSecurity.authenticationProvider(customAuthProvider);
return httpSecurity.build();
}
LDAP configuration class
#Configuration
public class LdapSecurityConfig {
#Value("${ldap.urls}")
private String ldapUrls;
#Value("${ldap.base.dn}")
private String ldapBaseDn;
#Value("${ldap.username}")
private String ldapSecurityPrincipal;
#Value("${ldap.password}")
private String ldapPrincipalPassword;
#Value("${ldap.user.dn.pattern}")
private String ldapUserDnPattern;
#Autowired
private CustomAuthoritiesPopulator customAuthoritiesPopulator;
#Autowired
private CustomUserDetailsMapper customUserDetailsMapper;
#Bean
public AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory managerFactory = new LdapBindAuthenticationManagerFactory(contextSource);
managerFactory.setUserDnPatterns(ldapUserDnPattern);
managerFactory.setUserDetailsContextMapper(customUserDetailsMapper);
managerFactory.setLdapAuthoritiesPopulator(customAuthoritiesPopulator);
managerFactory.setUserSearchFilter("sAMAccountName={0}");
return managerFactory.createAuthenticationManager();
}
#Bean
public LdapContextSource contextSource() {
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setUrl(ldapUrls);
ldapContextSource.setBase(ldapBaseDn);
ldapContextSource.setUserDn(ldapSecurityPrincipal);
ldapContextSource.setPassword(ldapPrincipalPassword);
return ldapContextSource;
}
}
In summary, user is being authenticated, but I do appear to actually have logged into the users account.
I'm currently working on implementing LDAP authentication on a project. It appears that the authentication portion of things are working in the sense that my application does accept the correct credentials. The issue I'm having is that I cant seem to access 'principal' in my jsp views. (I was able to access all of this before making the switch to LDAP). When running a trace my CustomUserDetails service is querying and pulling the correct account information. Any assistance is appreciated
This will display the proper username:
<sec:authorize access="isAuthenticated()">
<h2><sec:authentication property="name"/></h2>
</sec:authorize>
This does not (it did work before LDAP)
<sec:authorize access="isAuthenticated()">
<h2><sec:authentication property="principal.firstName"/></h2>
</sec:authorize>
Relevant Code
SecurityConfig.java
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.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.ldap.authentication.UserDetailsServiceLdapAuthoritiesPopulator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private CustomUserDetailsService userDetailsService;
#Bean
public CustomSaltSource customSaltSource(){ return new CustomSaltSource();}
#Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
return new AuthenticationSuccessHandler();
}
#Autowired
void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().contextSource()
.url("ldap://bar.foo.com")
.port(####)
.and()
.userDnPatterns("cn={0},cn=users,dc=ms,dc=ds,dc=foo,dc=com")
.ldapAuthoritiesPopulator(new UserDetailsServiceLdapAuthoritiesPopulator(userDetailsService));
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/skins/**", "/css/**", "/**/laggingComponents", "/assets/**").permitAll().and()
.formLogin().loginPage("/login").permitAll().defaultSuccessUrl("/", true).successHandler(myAuthenticationSuccessHandler())
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).deleteCookies("JSESSIONID").permitAll()
.and().authorizeRequests().antMatchers("/api/**").anonymous()
.and().authorizeRequests().anyRequest().authenticated().and().rememberMe().key("KEY").userDetailsService(userDetailsService);
}
#Override
public void configure(WebSecurity web) throws Exception {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new PermissionEvaluator());
web.expressionHandler(handler);
web.ignoring().antMatchers( "/skins/**", "/css/**", "/api/**", "/assets/**", "/health"); //"/**/test/**"
}
}
CustomUserDetaulsService.java
import org.hibernate.Session;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Set;
#Service
public class CustomUserDetailsService implements UserDetailsService{
#Override
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
Session session = DBFactory.factory.openSession();
User user = (User) session.createQuery("from User where userName =:userName")
.setParameter("userName", username).uniqueResult();
if(user == null){
throw new UsernameNotFoundException("User Not Found");
}
//Needed to initialize permissions
Set<Role> roles = user.getRoles();
int i = roles.size();
for(Role role: roles){
int j = role.getPermissions().size();
}
CustomUserDetails userDetails = new CustomUserDetails(user);
session.close();
return userDetails;
}
}
If I'm not wrong,
You switched to Ldap Authorization, set url and DN patterns but still provide userDetailsService which search user in database.
You need to set UserDetailsContextMapper by implementing the interface and creating your custom one. This will map data from ldap directory context to your custom UserDetails and return it through mapUserFromContext method.
Here is an example CustomUserDetailsContextMapper:
public class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
private LdapUser ldapUser = null;
private String commonName;
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
UserDetails ldapUserDetails = (UserDetails) super.mapUserFromContext(ctx,username,authorities);
try {
commonName = attributes.get("cn").get().toString();
} catch (NamingException e) {
e.printStackTrace();
}
ldapUser = new LdapUser(ldapUserDetails);
ldapUser.setCommonName(commonName);
return ldapUser;
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
My custom LdapUser:
public class LdapUser implements UserDetails
{
private String commonName;
private UserDetails ldapUserDetails;
public LdapUser(LdapUserDetails ldapUserDetails) {
this.ldapUserDetails = ldapUserDetails;
}
#Override
public String getDn() {
return ldapUserDetails.getDn();
}
#Override
public void eraseCredentials() {
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetails.getAuthorities();
}
#Override
public String getPassword() {
return ldapUserDetails.getPassword();
}
#Override
public String getUsername() {
return ldapUserDetails.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return ldapUserDetails.isAccountNonExpired();
}
#Override
public boolean isAccountNonLocked() {
return ldapUserDetails.isAccountNonLocked();
}
#Override
public boolean isCredentialsNonExpired() {
return ldapUserDetails.isCredentialsNonExpired();
}
#Override
public boolean isEnabled() {
return ldapUserDetails.isEnabled();
}
}
Then set CustomUserDetailsContextMapper in auth configuration. This is how you will be able to get your user from authentication.getPrincipal().
I hope I correctly understand your problem and answered.
I had to test LDAP auth + different roles for devs,admins,users
Many thanks to #Yernar Arystanov
The code is not so clean but works...
public class LdapUser implements UserDetails
{
private String commonName;
private List<String> groups;
private UserDetails ldapUserDetails;
public LdapUser(LdapUserDetails ldapUserDetails) {
this.ldapUserDetails = ldapUserDetails;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return ldapUserDetails.getAuthorities();
}
#Override
public String getPassword() {
return ldapUserDetails.getPassword();
}
#Override
public String getUsername() {
return ldapUserDetails.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return ldapUserDetails.isAccountNonExpired();
}
#Override
public boolean isAccountNonLocked() {
return ldapUserDetails.isAccountNonLocked();
}
#Override
public boolean isCredentialsNonExpired() {
return ldapUserDetails.isCredentialsNonExpired();
}
#Override
public boolean isEnabled() {
return ldapUserDetails.isEnabled();
}
public String getCommonName() {
return commonName;
}
public void setCommonName(String commonName) {
this.commonName = commonName;
}
public List<String> getGroups() {
return groups;
}
public void setGroups(List<String> groups) {
this.groups = groups;
}
}
Suggestion by #Ivan Baranuk was correct (extends LdapUserDetailsMapper):
public class CustomUserDetailsContextMapper extends LdapUserDetailsMapper {
private LdapUser ldapUser = null;
private String commonName;
private List<String> groups = new LinkedList<String>();
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
UserDetails ldapUserDetails;
ldapUserDetails = (UserDetails) super.mapUserFromContext(ctx,username,authorities);
try {
commonName = attributes.get("cn").get().toString();
Arrays.stream(ctx.getObjectAttributes("memberOf"))
.iterator()
.forEachRemaining( m -> {
groups.add(m.toString());
});
} catch (NamingException | javax.naming.NamingException e) {
e.printStackTrace();
}
ldapUser = new LdapUser((LdapUserDetails) ldapUserDetails);
ldapUser.setCommonName(commonName);
ldapUser.setGroups(groups);
return ldapUser;
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
then finally in SecurityConfig:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
public void configure(AuthenticationManagerBuilder auth) throws Exception {
...
auth.ldapAuthentication()
.userSearchBase("enter your search base")
.userSearchFilter("(&(objectClass=user)(sAMAccountName={0}))")
.userDetailsContextMapper(ldapUserDetailsMapper())
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator())
.contextSource()
.url(yourProperties.getLdapUrl())
.managerDn(yourProperties.getManagerDn())
.managerPassword(yourProperties.getManagerPassword());
}
private LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
return new LdapAuthoritiesPopulator() {
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData,
String username) {
LinkedList<SimpleGrantedAuthority> res = new LinkedList();
Arrays.stream(userData.getObjectAttributes("memberOf"))
.iterator()
.forEachRemaining( m -> {
if(m.toString().equals("your_dev_group"))
res.add(new SimpleGrantedAuthority("DEV_USER"));
if(m.toString().equals("your_admin_group"))
res.add(new SimpleGrantedAuthority("ADMIN_USER"));
});
if(res.isEmpty())
return Arrays.asList(new SimpleGrantedAuthority("USER"));
else
return res;
}
};
}
#Bean
#Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
#Bean
protected LdapUserDetailsMapper ldapUserDetailsMapper() {return new CustomUserDetailsContextMapper();}
}
At the end - some simple get method(authorized users only):
#RequestMapping("/logger")
public String testLogger(Authentication authentication) {
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_DEV_USER"))) {
LdapUser userDetails = (LdapUser) authentication.getPrincipal();
log.info("WELCOME :" + userDetails.getCommonName());
userDetails.getGroups().iterator().forEachRemaining((g) - > log.info("group: " + g.toString()));
}
return ""
}
I have a spring boot application i have many endpoint in this application. When i am hitting endpoint simultaneously JSON response from two different endpoint got interchanged.
For example:
i am hitting /currency/list endpoint and /fee endpoint and i am getting /fee endpoint data in currency/list endpoint and vice versa.
I have no idea why this happening. If anybody can suggest why happening will be helpful.
Also I am using spring security token based auth in this project
CurrencyController.java
#RestController
#RequestMapping(value = UrlConstant.BASE_ADMIN_URI_V1)
#Api(value = "Admin Controller")
#Scope("request")
public class CurrencyController {
public static final Logger logger = LoggerFactory.getLogger(CurrencyController.class);
#Autowired
private LocaleService localService;
#RequestMapping(value = UrlConstant.CURRENCY_LIST_FOR_MARKET, method = RequestMethod.GET)
public ResponseEntity<Object> getCurrencyListForMarket() {
List<Currency> currencyList = currencyService.getCurrencyListForMarket();
ObjectMapper mapper = new ObjectMapper();
try {
String stringList = mapper.writeValueAsString(currencyList);
logger.debug("all currency list as String: {}", stringList);
} catch (JsonProcessingException e) {
logger.debug("error in currency list: {}", e.getMessage());
e.printStackTrace();
}
return ResponseHandler.response(HttpStatus.OK, false, localService.getMessage("currency.list.success"),
currencyList);
}
}
AdminController.java
#RestController
#RequestMapping(value = UrlConstant.BASE_ADMIN_URI_V1)
#Api(value = "Admin Controller")
#Scope("request")
public class AdminController {
#Autowired
private LocaleService localeService;
#Autowired
private FeeService feeService;
#RequestMapping(value = UrlConstant.TRADING_FEES, method = RequestMethod.GET)
public ResponseEntity<Object> getTradingFees() {
TradingFee fee = tradingFeeService.getTradingFee();
return ResponseHandler.response(HttpStatus.OK, true,
localeService.getMessage("admin.transaction.fees.found.success"), fee);
}
}
TokenAuthenticationFilter.java
public class TokenAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
// extract token from header
String token = httpRequest.getHeader("Authorization");
if (token != null && !token.isEmpty()) {
AuthenticationTokenRepo authenticationTokenRepository = WebApplicationContextUtils
.getRequiredWebApplicationContext(httpRequest.getServletContext())
.getBean(AuthenticationTokenRepo.class);
// check whether token is valid
AuthenticationToken authToken = authenticationTokenRepository.findByToken(token);
if (authToken != null) {
// Add user to SecurityContextHolder
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
authToken.getUser(), null, new ApplicationUserDetail(authToken.getUser()).getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
SecurityContextHolder.clearContext();
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment environment;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers(HttpMethod.POST, "/api/v1/login").permitAll()
.antMatchers(HttpMethod.POST, "/api/v1/user/register").permitAll().anyRequest().authenticated();
// Implementing Token based authentication in this filter
final TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter();
http.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(UrlConstant.BASE_ADMIN_URI_V1 + UrlConstant.CURRENCY_LIST_FOR_MARKET);
web.ignoring().antMatchers(UrlConstant.BASE_ADMIN_URI_V1 + UrlConstant.TRADING_FEES);
}
}
}
ApplicationUserDetail.java
public class ApplicationUserDetail implements UserDetails,Serializable {
private static final long serialVersionUID = 1L;
transient User user;
public ApplicationUserDetail(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole().getName());
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmailId();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return user.getIsEnabled();
}
}
More info: When i am printing response in my api it is correct but when i am printing it in my Authentication filter i got the response of fee api in my currency api so i think there is some problem between api to filter.
i am a newbie to spring
was not able to implement login to the application, actually could not figure out where its wrong.
followed a youtube video to do all this.
Help would be very much appreciated.
When i try to log in the application wont allow to log in.
console logs shows querys are being executed but cant log into the system.
also the password are saved in plain ASCII.
WebSecurityConfig class
#Configuration
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AccessDeniedHandler accessDeniedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
// roles admin allow to access /actuator/**
// roles user allow to access /Application/**
// custom 403 access denied handler
#Override
protected void configure(HttpSecurity http) throws Exception {
// some antMatchers permit all
}
}
customUserDetailsService class
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
private final UserRepo userRepo;
#Autowired
public CustomUserDetailsService(UserRepo userRepo) {
this.userRepo = userRepo;
}
#Override
// userId is reffered as username
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user=userRepo.findByUsername(userName);
if(null == user){
System.out.println("\n\n\n No user present with username: "+userName);
throw new UsernameNotFoundException("No user present with username: "+userName);
}else{
CustomUserDetails c =new CustomUserDetails(user);
//System.out.println(c.getAuthorities());
return c;
}
}
}
CustomUserDetails class
public class CustomUserDetails extends User implements UserDetails{
private static final long serialVersionUID = 1L;
public CustomUserDetails(User user){
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<UserRole> roles = super.getUserRole();
List<String> userRoles= new ArrayList<String>();
for(UserRole r : roles) {
userRoles.add(r.getRole().toString());
}
String strRoles=StringUtils.collectionToCommaDelimitedString(userRoles);
return AuthorityUtils.commaSeparatedStringToAuthorityList(strRoles);
}
#Override
public boolean isAccountNonExpired() {
return super.isAccountNonExpired();
}
#Override
public boolean isAccountNonLocked() {
return super.isAccountNonLocked();
}
#Override
public boolean isCredentialsNonExpired() {
return super.isCredentialsNonExpired();
}
#Override
public boolean isEnabled() {
return super.isEnabled();
}
#Override
public String getUsername() {
return super.getUsername();
}
#Override
public String getPassword() {
return super.getPassword();
}
}
I store information about users in seperated tables owners,employees,users i am trying to use java configuration in spring security.
I have created three diferent authentication providers for each user type, but only the Users Provider is being triggered. I have read the spring security docs and the only way to do this seems to be is create class with multiple embedded classes extended from WebSecurityConfigurerAdapter but i don't want to do it this way because it requires a lot of duplicating code, is there any other way
I tryed to use the simple userDetailService inside which i send request to all tables in databese but still there is not results, only one query is buing executed and nothing, the only responce i get is:
2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1]
.s.a.DefaultAuthenticationEventPublisher : No event was found for the
exception
org.springframework.security.authentication.InternalAuthenticationServiceException
2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1]
o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for
failed:
org.springframework.security.authentication.InternalAuthenticationServiceException:
No entity found for query; nested exception is
javax.persistence.NoResultException: No entity found for query
But i never throw any exception!! And the most strange is that i can see in the debugger how the execution rapidly stops right after em.createQuery(..).getSingleResult().. and that's it, nothing more! There is no return statement no exception nothing, wtf!!
This is part of my current configuration:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(createAuthenticationProvider(employeeDetailService()))
.authenticationProvider(createAuthenticationProvider(ownerDetailsService()))
.authenticationProvider(createAuthenticationProvider(userDetailsService()));
}
#Bean
public OwnerDetailsService ownerDetailsService() {
return new OwnerDetailsService();
}
#Bean
public EmployeeDetailServiceImpl employeeDetailService() {
return new EmployeeDetailServiceImpl();
}
#Bean
public UserDetailsServiceImpl userDetailsService() {
return new UserDetailsServiceImpl();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(6);
}
#Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return new MySimpleUrlAuthenticationSuccessHendler();
}
private AuthenticationProvider createAuthenticationProvider(UserDetailsService service) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(service);
provider.setPasswordEncoder(passwordEncoder());
provider.setHideUserNotFoundExceptions(true);
return provider;
}
User detail services:
#Service
public abstract class CustomUserDetailService implements UserDetailsService{
#Autowired
IDBBean dao;
protected CustomUserDetails getUser(GetUserByNameFunction function, String name) {
return createUser(function.get(name));
}
protected CustomUserDetails createUser(Authenticational user) {
return new CustomUserDetails(user, getAuthorities(user.getAuthority()));
}
protected List<GrantedAuthority> getAuthorities(String authority) {
return Collections.singletonList(new SimpleGrantedAuthority(authority));
}
}
Implementations
public class EmployeeDetailServiceImpl extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return super.getUser(dao::getEmployeeByEmail, email);
}
}
public class OwnerDetailsService extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return super.getUser(dao::getOwnerByEmail, email);
}
}
public class UserDetailsServiceImpl extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
return super.getUser(dao::getUserByEmail, userName);
}
}
Custom user details:
private Long id;
private String userEmail;
public CustomUserDetails(Authenticational user,
Collection<? extends GrantedAuthority> authorities) {
super(
user.getName(),
user.getPassword().toLowerCase(),
user.isEnabled(),
true,
true,
true,
authorities);
upadateValues(user);
}
private void upadateValues(Authenticational user) {
this.id = user.getId();
this.userEmail = user.getEmail();
}
Just to clarify something from the other answer:
Your authentication providers are stored in a list inside ProviderManager that iterates your authentication request through them. If your authentication provider throws AuthenticationException (BadCredentialsException extends AuthenticationException), then the ProviderManager will try another provider. If you set the hideUserNotFoundExceptions property, then it will also wrap and ignore UsernameNotFoundException and try another provider in this case too.
If I were you I would start by placing a debugging point inside ProviderManager's authenticate method. From there you can find out why the other authentication providers are not being called for their authenticate method.
Also I would think about having only one authentication provider with one UserDetailsService. It seems to me that you are doing a lot of complex not really needed operations like passing function to your abstract implementation when all you could do would be to have one UserDetailsService that would ask all your DAOs for a user. Which is basically what you're trying to accomplish but minus 2 authentication providers, minus 1 abstract class and minus 2 UserDetailsService implementations.
Spring Security will not try other authentication providers if a provider throws an AccountStatusException or if a UserDetailsService throws a UserNameNotFoundException or any other AuthenticationException
If you want other providers to be tried, then the loadUserByUserName methods of your UserDetailsServiceImpl and OwnerDetailsService should not throw the UserNameNotFound exception.
You should decide if you either want to return a dummy anonymous UserDetails object that will be used exclusively for fallback or some other mechanism to not throw the exception when a user is not available in your UserDetailsService implementation
this is an example I'v done to provide multi-auth for my application which have two diffirent user : admin and client .
ps: the admin and the client are two diffirent model.
public class CustomUserDetails implements UserDetails{
private Admin admin;
private Client client;
public CustomUserDetails(Admin admin) {
this.admin = admin;
}
public CustomUserDetails(Client client) {
this.client = client;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public String getPassword() {
if((admin != null)&&(client==null)) {
return admin.getPassword();
}
else {
return client.getPassword();
}
}
#Override
public String getUsername() {
if((admin != null)&&(client==null)) {
return admin.getEmail();
}
else {
return client.getMail();
}
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
the CustomUserDetailsService class :
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
AdminRepository adminRepo;
#Autowired
ClientRepository clientRepo;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Admin admin = adminRepo.findByEmail(email);
Client client = clientRepo.findByEmail(email);
if((admin == null)&&(client==null)) {
throw new UsernameNotFoundException("User not found");
}
else if((admin != null)&&(client==null)) {
return new CustomUserDetails(admin);
}
else {
return new CustomUserDetails(client);
}
}
}