I was wondering if it is possible to create Spring Webservice with BasicAuth without creating web.xml or any xml based configuration stuff.
I saw ton of tutorials to do it with xml, but I'd like to do this in class configuration way.
Hm I'm sorry this may not be exact because I don't use this implementation anymore, I replaced with EE-based model BUT.... work with me here. :)
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("fooserviceuser").password("$2a$10$9DvfxB.Sj2B/QznFRw85FenDvhUGglWWgOR7mmal/jNImhdHQRJgi").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/**").hasRole("USER")
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
You also need an initializer to hook into the container like --
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
This uses bcrypt for password hashing. Sorry this isn't working code but I think there is enough here to put you on the right track! Google around for similar code snippets, they're there.
Related
I have a web application that uses Spring Security. It works on Tomcat, but didn't on Weblogic 12.2.1.2.
On Weblogic, user isn't redirected to the login page when tries to reach a restricted URL (for example localhost:7001/website/restricted/welcome). On Tomcat the user is correclty redirected to the login page.
I read that this is a bug of Weblogic 12.1 and it seems to be fixed in Weblogic 12.2. But I'm using Weblogic 12.2.1.2 and I enconter the same problem.
I read some solutions, but I have difficult to understand them, since I have a different Spring configuration.
These are my classes about Spring Security.
This is the class that extends WebSecurityConfigurerAdapter.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("username1")
.password(passwordEncoder()
.encode("password1"))
.roles("ADMIN");
}
#Bean
public PasswordEncoder passwordEncoder() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.formLogin()
.loginPage("/login")
.usernameParameter("userId")
.passwordParameter("password");
httpSecurity.formLogin()
.defaultSuccessUrl("/")
.failureHandler(new CustomAuthenticationFailureHandler())
.and()
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true);
httpSecurity.logout()
.logoutSuccessUrl("/login?logout");
httpSecurity.rememberMe()
.rememberMeParameter("rememberMe")
.key("rememberMeKey")
.tokenValiditySeconds(1800);
httpSecurity.exceptionHandling()
.accessDeniedPage("/login?accessDenied");
httpSecurity.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/**/add").access("hasRole('ADMIN')")
.antMatchers("/**/market/**").access("hasRole('USER')");
httpSecurity.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
This is the class that extends AbstractSecurityWebApplicationInitializer.
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer
implements WebApplicationInitializer {
#Override
protected boolean enableHttpSessionEventPublisher() {
return true;
}
}
The problem seems to be bound to Spring Boot.
When, with Spring Tool Suite, I use the Spring Starter Project wizard (Spring boot) I encounter the problem.
If I don't use Spring Boot, Spring Security work properly!
How should I fix this problem?
Thank you
I'm fighting a "bug" similar to one Apple had recently in OS X :) An application authenticates users treating their password field not only as a bcrypt hash but also as a plaintext, so it allows special utility accounts to log in with an empty password.
There are a bunch of user records in the database and almost all of them has their password hashed with bcrypt. There are however a few special utility accounts with password hash field deliberately left empty (to make BcryptPasswordEncoder#matches always reject login attempts for them).
Placing breakpoints all over ProviderManager I can see multiple authentication providers initialized by spring:
a "proper" DaoAuthenticationProvider with bcrypt encoder
AnonymousAuthenticationProvider, which nobody configured, but at least I can guess it came from permitAll() or something alike.
an unwanted DaoAuthenticationProvider with PlaintextPasswordEncoder which spoils all fun
We have another project where we don't use Spring Boot and with almost identical configuration it works as expected (passwords are never treated as plaintext, only as bcrypt hash). So my guess is: this "problem" has something to do with Spring Boot "configuration by convention" and I can't find how to override its behavior.
In this project I use the following configuration:
#Configuration
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
AuthenticationProvider authenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(userDetailsService)
.authorizeRequests()
.antMatchers("/js/**", "/css/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/j_spring_security_check").permitAll()
.successHandler(new SuccessHandler())
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll()
.logoutSuccessUrl("/login");
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
}
#Autowired
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider);
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(15);
}
}
Edit: If I get it correct, there's a way to configure global and local AuthenticationManagerBuilders:
// Inject and configure global:
/*
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
*/
// Override method and configure the local one:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
Doing that, I have now two instances of builder: one - local - has only the correct manager: bcrypt, the other one - global - has other 2 providers: anonymous and plaintext. Authentication behavior persists, application still uses both and lets users login with plaintext passwords. Uncommenting configureGlobal doesn't help too, in that case, global manager contains all three providers.
The configuration is explicitly providing the userDetailsService in multiple places without providing the PasswordEncoder. The easiest solution is to expose the UserDetaisService and PasswordEncoder as a Bean and delete all explicit configuration. This works because if there is no explicit configuration, Spring Security will discover the Beans and create authentication from them.
#Configuration
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http // Don't forget to remove userDetailsService
.authorizeRequests()
.antMatchers("/js/**", "/css/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/j_spring_security_check").permitAll()
.successHandler(new SuccessHandler())
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll()
.logoutSuccessUrl("/login");
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
}
// UserDetailsService appears to be a Bean somewhere else, but make sure you have one defined as a Bean
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(15);
}
}
The reason it is failing is because there is explicit configuration to use the UserDetailsService twice:
#Autowired
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// below Configures UserDetailsService with no PasswordEncoder
auth.userDetailsService(userDetailsService);
// configures the same UserDetailsService (it was used to create the authenticationProvider) with a PasswordEncoder (it was provided to the authenticationProvider)
auth.authenticationProvider(authenticationProvider);
}
If you want explicit configuration, you could use the following
#Override
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
and delete the authenticationProvider bean along with the #Autowired AuthenticationProvider. Alternatively, you could just use the AuthenticationProvider, but not both.
Generally, the explicit configuration of AuthenticationManagerBuilder is only needed when you have multiple WebSecurityConfigurerAdapter with different authentication mechanisms. If you do not have the need for this, I recommend just exposing the UserDetailsService and (optionally) the PasswordEncoder as a Bean.
Note that if you expose a AuthenticationProvider as a Bean it is used over a UserDetailsService. Similarly, if you expose an AuthenticationManager as a Bean it is used over AuthenticationProvider. Finally, if you explicitly provide AuthenticationManagerBuilder configuration, it is used over any Bean definitions.
It turned out that protected void configure(HttpSecurity http) was triggering the second AuthenticationManagerBuilder creation. So I provided my AuthenticationProvider bean and added it to httpsecurity configuration. Everything seems to work as expected now. It might not be the correct solution though.
New configuration (works for me):
#Configuration
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
AuthenticationProvider authenticationProvider;
#Autowired
PasswordEncoder passwordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authenticationProvider(authenticationProvider) // <== Important
//.anonymous().disable() // <== This part is OK. If enabled, adds an anonymousprovider; if disabled, it is impossible to login due to "unauthenticated<->authenticate" endless loop.
.httpBasic().disable()
.rememberMe().disable()
.authorizeRequests()
.antMatchers("/js/**", "/css/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/j_spring_security_check").permitAll()
.successHandler(new SuccessHandler())
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll()
.logoutSuccessUrl("/login");
}
#Bean
public AuthenticationProvider authenticationProvider() {
final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(15);
}
}
I want to restrict some JSP pages according to the UserRole in my spring boot app
for this i have seen so many examples like:-
http.authorizeRequests().antMatchers("/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
but my problem is i cant hardcode the UserRoles like ADMIN or USER because i have new user roles created in different occasions so i can't hardcode the exact user-roles.i have the information about the user-roles that can access a list of jsp pages in my database and here iam using spring security and iam newbie to spring boot and spring security.
edit
my config class is
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Autowired
CustomAuthHandler customAuthenticationHandler;
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
System.out.println(1);
return new BCryptPasswordEncoder();
}
#Bean
CustomAuthHandler authenticationHandler() {
return new CustomAuthHandler();
}
/*#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/edu/assets/**");
}
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/edu/**","/Login**","/UserSignUP","/organization**","/email_availablity").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/Login").usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/index1",true).failureHandler(customAuthenticationHandler).permitAll()
.and()
.logout()
.permitAll()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
Finally i found a answer...
1.Create an interceptor class in your spring boot
public class MyCustomInterceptor implements HandlerInterceptor{
//unimplemented methods comes here. Define the following method so that it
//will handle the request before it is passed to the controller.
#Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response){
//your custom logic here.(for request validating)
return true;
}
}
2.Define a configuration class
#Configuration
public class MyConfig extends WebMvcConfigurerAdapter{
#Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new MyCustomInterceptor()).addPathPatterns("/**");
}
}
thats all now your requests will pass through the logic defined under preHandle() method of MyCustomInterceptor before pass through controller
Firstly, you are validating api not .jsp pages.
#Configuration annotation is used in java file to configure your application instead of xml configuration. From the spring's official doc:
Indicates that a class declares one or more #Bean methods and may be
processed by the Spring container to generate bean definitions and
service requests for those beans at runtime.
So, When you annotated a class with #Configuration the methods are executed just for a time at the very beginning when you start your program. That's why you can't dynamically check UserRoles from the below method:
#Override
protected void configure(HttpSecurity http) throws Exception {
// inner codes
}
So, you have to hard coded(in your .java file or .properties file) if you want to authorize from that method.
The Solution
I have noticed that you are using UserDetailsService for the login purpose. You can modify the loadUserByUsername(String username) method and dynamically check authorization for users.
Steps:
Store the complete user information: login, password and Roles in a database. For this you need 3 Tables: 1. User, 2. Roles and 3. User_Roles. Each time while login, you need to set the UserRoles(roles are read from database) to the UserInfo object.
Then you can add a Interceptor(as a url filter) to your project to check each api(or url) whether Logged user is authorized for the api or not. For this, you can query database with the username for the roles and compare with the requested uri.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/hello").permitAll()
.antMatchers("/secure/hello").authenticated()
.and()
.httpBasic()
.realmName("KS TEST")
.and()
.csrf()
.disable();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
http.logout().logoutSuccessUrl("/");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
I have pasted part of above code. I also extended three classes and injected them as bean AuthenticationEntryPoint, SimpleUrlAuthenticationFailureHandler, SimpleUrlAuthenticationSuccessHandler thinking I could extend those and try to get custom error message in case of auth failure. I get the standard spring auth failure in my REST API that works perfectly but I want to define my own class that I want to send as response at auth layer even before the resource endpoint comes into play. Like my own custom class with my own data members. Currently I get the default error in case of wrong auth
{"timestamp":1469955305299,"status":401,"error":"Unauthorized","message":"Bad credentials","path":"/secure/hello"}
This is how i execute the rest call with wrong pwd
//REST API execution example
curl -v -u mickey:cheesee http://localhost:8080/secure/hello
If i give the right pwd things work as expected. However in wrong one, say I want to have a class that I can populate and that becomes json reponse at the auth layer. Can someone tell me what I need to do?
I am trying to setup a single path (/basic) in my spring-boot spring MVC based application to be basic auth protected. I am just going to configure this using my own custom configuration parameters so the username and password are simply "admin" and "admin".
This currently works for the /basic path (I am prompted and can login correctly). The problem is that logout does not work (and I am not sure why) and also other paths (like /other shown) are being asked for basic auth credentials (before always being denied).
static class MyApplicationSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/open").permitAll();
http.authorizeRequests().antMatchers("/other").denyAll(); // Block it for now
http.authorizeRequests().antMatchers("/basic").authenticated().and().httpBasic().and().logout().logoutUrl("/basic/logout").invalidateHttpSession(true).logoutSuccessUrl("/");
}
}
I expected /other to always be denied but I don't get why basic auth is coming up for it. /open works as expected. I also don't understand why /basic/logout does not log me out (it also does not produce error messages). I do have a simple bit of code as a placeholder for the logout endpoint but if I do not have that then I get a 404. The "home" view is my web app root so I just want to send the user there after logout.
#RequestMapping("/logout")
public ModelAndView logout() {
// should be handled by spring security
return new ModelAndView("home");
}
UPDATE:
Here is the solution that seemed to work in the end (except the logout part, still not working):
#Configuration
#Order(1) // HIGHEST
public static class OAuthSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth").authorizeRequests().anyRequest().denyAll();
}
}
#Configuration
public static class BasicAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/basic").authorizeRequests().anyRequest().authenticated().and().httpBasic();
http.logout().permitAll().logoutUrl("/logout").logoutSuccessUrl("/").invalidateHttpSession(true);
//.and().logout().logoutUrl("/basic/logout").invalidateHttpSession(true).logoutSuccessUrl("/");
}
}
i'm not sure about the logout, but we had a similar problem with having some of our site under basic and some of it not. Our solution was to use a second nested configuration class only for the paths that needed http basic. We gave this config an #Order(1)..but i'm not sure if that was necessary or not.
Updated with code
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private static final Logger LOG = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
public void registerAuthentication(AuthenticationManagerBuilder auth, Config appConfig) throws Exception {
auth.inMemoryAuthentication()
.withUser(appConfig.getString(APIConfig.CONFIG_KEY_MANAGEMENT_USER_NAME))
.password(appConfig.getString(APIConfig.CONFIG_KEY_MANAGEMENT_USER_PASS))
.roles(HyperAPIRoles.DEFAULT, HyperAPIRoles.ADMIN);
}
/**
* Following Multiple HttpSecurity approach:
* http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#multiple-httpsecurity
*/
#Configuration
#Order(1)
public static class ManagerEndpointsSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/management/**").authorizeRequests().anyRequest().hasRole(HyperAPIRoles.ADMIN).and()
.httpBasic();
}
}
/**
* Following Multiple HttpSecurity approach:
* http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#multiple-httpsecurity
*/
#Configuration
public static class ResourceEndpointsSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//fyi: This adds it to the spring security proxy filter chain
.addFilterBefore(createBBAuthenticationFilter(), BasicAuthenticationFilter.class)
;
}
}
}
this seems to secure the actuator endpoints at /management with basic auth while the others work with a custom auth token header. We do not prompt for credentials (no challenge issued) though for anything..we'd have to register some other stuff to get that going (if we wanted it).
Hope this helps
only one path will be protected
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception
{
auth.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("user"))
.roles("USER");
}
#Configuration
#Order(1)
public static class ManagerEndpointsSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/add/**").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic()
.and().csrf().disable();
}
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}