I have an extremely simple sample app here: https://github.com/timtebeek/anonymous-principal
Relevant bits copied below:
ResourceConfig.java
#Configuration
#EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").permitAll()
.anyRequest().denyAll();
// Anonymous user should authenticate as guest for authorization
http.anonymous().principal("guest");
}
#Override
public void configure(final ResourceServerSecurityConfigurer resources) {
resources.resourceId("myresource");
}
}
DemoApplication
#SpringBootApplication
#RestController
#SuppressWarnings("static-method")
public class DemoApplication {
public static void main(final String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#RequestMapping(value = "/principal", method = RequestMethod.GET)
public String get(final Principal user) {
Assert.notNull(user);
return user.getName();
}
#RequestMapping(value = "/authprincipal", method = RequestMethod.GET)
public String get(#AuthenticationPrincipal final String user) {
Assert.notNull(user);
return user;
}
#RequestMapping(value = "/authentication", method = RequestMethod.GET)
public String get() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Assert.notNull(auth);
return auth.getName();
}
}
In this setup both /authprincipal and /authentication work, but /principal fails when the user is not authenticated, as the principal argument is null. I'd wanted to use the plain Principal rest method argument with my anonymous users as well, as that gives me the cleanest code.
What can I do to make Principal argument in my rest methods work for anonymous users?
Related
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsInfoService userDetailsService;
#Autowired
private CustomLoginAuthenticationProvider authenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.userDetailsService(userDetailsService);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/api/user/authenticate/**")
.antMatchers("/api/user/**")
.antMatchers("/api/master/**")
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/* #EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}*/
}
This is my spring security configuration in the application.
It works fine most of the scenarios, but not able to track one issue.
Whenever I call a web service with a date format spring security returns 401 error.
http://localhost:9190/nazouki/api/master/create
with input
{"orgId":"11","birthDate":"2020-04-20T20:00:00.000Z"}
without date it works fine.
Controller code
#RequestMapping(value = "master/create", method = RequestMethod.POST)
public #ResponseBody ResultDecorator createMaster(#RequestBody TuMasterDto tuMasterDto){ handler.resolveResult(masterService.createMaster(tuMasterDto), OperationEnum.SAVE);
}
Exception has been thrown from the CustomAuthenticationEntryPoint in the spring security.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException ae) throws IOException, ServletException {
log.info("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
}
}
We can't say for sure unless we also have a look at the handler.resolveResult() and the masterService.createMaster() methods.
My initial assumption would be that you have a GlobalMethodSecurity configuration on one or both of these methods (or a code path that is used by them), and since you ignore the security for the path web.ignoring().antMatchers("/api/master/**"), there is no SecurityContext created and therefore you get that response.
As to why it behaves differently when you add the date than without, I'm not so sure myself. Maybe you have 2 implementations? something like this
public void handleRequest(Dto dto) {
if(dto.getDate() != null) {
doSomethingWithTheDate(dto.getDate());
}
// handle normally
}
#PreAuthorize("hasRole('ROLE_USER')")
public void doSomethingWithTheDate(Date date) {
...
}
I managed to configure OAuth2 and ldap authorization. Created custom LdapUser by implementing LdapUserDetails and CustomUserDetailsContextMapper by implementing UserDetailsContextMapper.
Eventually, I get access token when authorize by Active Directory username and password.
But the problem is, I cannot get my current logged user from SecurityContextHolder.getContext().getAuthentication() as it says
java.lang.String cannot be cast to LdapUser
Below my security configure:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(adAuthenticationProvider())
.ldapAuthentication()
.userSearchBase("ldap.searchbase").userSearchFilter("ldap.filter").groupSearchFilter("ldap.groupsearch")
.contextSource(contextSource())
.userDetailsContextMapper(userDetailsContextMapper())
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource(Arrays.asList("ldap.url"), "dc=smth,dc=com");
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider adAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("smth.com","ldap.url");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
return provider;
}
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new CustomUserDetailsContextMapper();
}
Custom LdapUser:
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.ldap.userdetails.LdapUserDetails;
import java.util.Collection;
public class LdapUser implements LdapUserDetails
{
private String commonName;
private LdapUserDetails 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();
}
}
CustomUserDetailsContextMapper:
I can print out context attributes successfully and I see that this is my logged user
#Configuration
public class CustomUserDetailsContextMapper extends LdapUserDetailsMapper implements UserDetailsContextMapper {
private LdapUser ldapUser = null;
private String commonName;
private Boolean isCity;
#Override
public LdapUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Attributes attributes = ctx.getAttributes();
LdapUserDetails ldapUserDetails = (LdapUserDetails) super.mapUserFromContext(ctx,username,authorities);
return new LdapUser(ldapUserDetails);
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
And now this is how i want to get custom LdapUser:
public LdapUser getCurrentLdapUser() {
org.springframework.security.core.context.SecurityContext securityContext = SecurityContextHolder
.getContext();
Authentication authentication = securityContext.getAuthentication();
LdapUser user = null;
if (authentication != null) {
user = ((LdapUser) authentication.getPrincipal());
}
return user;
}
After this function is called I get casting error. When I try to get principal name it returns - anonymousUser. I have no idea why it is not returning me LdapUser
Okay, I got the answer. Missed the basic things.
Since I did not configure Resource Server (ResourceServerConfigurerAdapter) every logged Active Directory user considered as anonymous. That is why, Security Context was returning String user instead of my custom Ldap user.
Here is an example ResourceServerConfig in case somebody will need:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource_id";
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.resourceId(RESOURCE_ID)
.stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.authorizeRequests()
.antMatchers("/api/**").hasAnyAuthority("Authority_1","Authority_2")
.and().exceptionHandling().authenticationEntryPoint(new UnauthorizedHandler())
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler());
}
#Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
}
Hello I am trying to get the username when there is a successful login. For managing logins I use a config java class as follows:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource
private UserDetailsService userService;
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/home",
"/error",
"/member/register",
"/css/**",
"/js/**",
"/fonts/**",
"**.html",
"/static/**",
"/public/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest()
.authenticated();
http
.formLogin()
.loginPage("/login")
.permitAll().and()
.logout()
.permitAll();
}
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.userDetailsService(userService);
}
}
And I have a simple UI for login. I believe I get the login username by implementing the interface UserDetails (documentation). I have to define how to getUsername() but currently I have to provide it a user instead of it giving me the current user logged in. Here is how I implemented it:
public class UserDetailsImpl implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 1L;
private Member member;
public UserDetailsImpl(Member member) {
this.member = member;
}
public UserDetailsImpl() {
// TODO Auto-generated constructor stub
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new HashSet<GrantedAuthority>(1);
// authorities.add(new SimpleGrantedAuthority(member.getRole()));
return authorities;
}
#Override
public String getPassword() {
return member.getPassword();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
#Override
public String getUsername() {
// TODO Auto-generated method stub
return member.getUsername();
}
}
The constructor that doesn't take parameters should be the current logged in user. But I am not sure how to do it.
Thanks.
To get the currently authenticated user in a controller you can add Pricipal as a parameter in the method signature.
#RequestMapping(value="/home")
public String home(Principal principal) {
/*
Spring will inject the prinical for you.
Then you can call getName() to get the username;
*/
String user = principal.getName();
// do stuff with user
return "home";
}
Ther are several ways to do this and this is just one. There is a good write on other ways and benefit of each here.
http://www.baeldung.com/get-user-in-spring-security
Authentication authentication = SecurityContextHolder.getContext().getAuthentication()
Then you can get the detail object from authentication, and get the login username from detail object, maybe you need to cast your own Class, more about the SecurityContextHolder, Authentication see here
SecurityContext securityContext = SecurityContextHolder.getContext();
Object obj = securityContext.getAuthentication().getPrincipal();
if (obj instanceof User) {
User springSecurityUser = (User) obj;
String userConnectedName = springSecurityUser.getUsername();
Collection<GrantedAuthority> listRole = securityContext.getAuthentication().getAuthorities();
}
I'm trying to create an oauth2-enabled auth server which is able to authenticate users with two authentication providers: the former is in-memory (for default user-passwords) the latter is an external LDAP server (by now i'm using the example from gs-authenticating-ldap-complete).
I'm able to successfully retrieve an access token for any user, but i'm only able to use the refresh token for retrieving a new token for any user that is registered in the LDAP server. While everything is fine if I try to refresh an in-memory user's token, with the LDAP ones I get: 401 Unauthorized { "error": "unauthorized", "error_description": "ben" } where "ben" is the user id.
As far as I know (after some debugging) the exception occurs in DefaultTokenServices.java:150.
In the following I report the configuration classes I'm using.
#Configuration
#EnableWebSecurity
#Order(6)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().csrf().disable();
}
#Configuration
protected static class DefaultUsersAuthConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN").and().withUser("guest")
.password("guest").roles("USER");
}
}
#Configuration
protected static class LDAPAuthConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userDnPatterns("uid={0},ou=people").groupSearchBase("ou=groups")
.userDetailsContextMapper(new MyLdapUserDetailsMapper()).contextSource()
.ldif("classpath:test-server.ldif");
}
}
protected static class MyLdapUserDetailsMapper extends LdapUserDetailsMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
final UserDetails originalUser = super.mapUserFromContext(ctx, username, authorities);
final Set<GrantedAuthority> newAuth = new HashSet<>(originalUser.getAuthorities());
newAuth.add(new SimpleGrantedAuthority("ROLE_EXTRA_ROLE"));
return new User(originalUser.getUsername(), originalUser.getPassword(), originalUser.isEnabled(),
originalUser.isAccountNonExpired(), originalUser.isCredentialsNonExpired(),
originalUser.isAccountNonLocked(), newAuth);
}
}
}
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends OAuth2AuthorizationServerConfiguration {
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("acme").secret("acmesecret")
.authorizedGrantTypes("password", "refresh_token", "client_credentials")
.scopes("read", "write", "openid").autoApprove(true);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore);
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests().antMatchers("/me").authenticated();
// #formatter:on
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
}
}
}
I'm using spring-boot 1.3.2.RELEASE. What am I missing?
I've a scenario where I need to let some request (let's say request method is GET) so that no 401 error can be thrown.
Below is my Spring Security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/bower_components/**")
.antMatchers("/fonts/**")
.antMatchers("/images/**")
.antMatchers("/scripts/**")
.antMatchers("/styles/**")
.antMatchers("/views/**")
.antMatchers("/i18n/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/app/rest/register")
.antMatchers("/app/rest/activate");
}
}
And this is my ResourceServerConfigurerAdapter implementation:
How can I allow requests?
There is a method called requestMatchers where you can call it with one or more RequestMatcher implementaions.
public void configure(HttpSecurity http){
.....
web.ignoring().requestMatchers(new MethodTypeRequestMatcher(RequestMethod.GET));
.....
}
And you can define your implementation:
public class MethodRequestMatcher implements RequestMatcher {
private RequestMethod method;
public MethodRequestMatcher(RequestMethod method) {
this.method = method;
}
#Override
public boolean matches(HttpServletRequest request) {
if (method == null) {
return false;
}
return request.getMethod().equals(method.name());
}
}
I think you can try like follows:
<code>
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.httpStrictTransportSecurity()
.xssProtection()
.frameOptions()
.and().authorizeRequests()
// PERMIT ALL
.antMatchers("/home").permitAll()
// UNAUTHENTICATED USER
.antMatchers("/ForgetPassword").anonymous()
// TO SPECIFIC PERSON
.antMatchers("/Report").access("hasRole('ADMIN') or hasRole('S_USER')");
}
</code>