If I have a superclass User, and classes Student and Lecturer extending this class, what is the best way to use roles (i.e. for security) in the springBoot application.
I feel like it is almost not necessary because I have two classes anyway?
Using the class types as roles is not a very good idea as it is not flexible. Lets say you create a user that is a Student and persist it into database table called student. If you later want to change the role of this user to a Lecturer, you will have to migrate all the users data to a new table.
A common approach that I often see being used is having a intermediate class (table) which holds all the available roles. Then each user has a list of roles (or only one role). This way you can easily change what roles apply to different users.
For example lets say you have a basic role class:
class Role {
String name;
// Getters and setters...
}
Then your student and lecturer classes would look something like this:
abstract class User {
List<Role> roles;
// Easy way to check if a user has a role (better approach would be to use a Set).
boolean hasRole(String name) {
for (Role r : roles) {
if (name.equals(r.getName()))
return true
}
return false
}
}
class Lecturer extends User {
}
class Student extends User {
}
This type of role structure has one obvious benefit - it will be very easy to change roles for the users on your database.
Also you can create multiple tiny roles. For example a Lecturer could have the roles read, write, create and so on, which would grant him all the rights to your application. While the student might only have the read role.
I would also suggest to checkout Spring Security if you plan to implement some sort of authentication to you application (it also follows a similar method of roles that I've described).
I would add security roles anyway.
You do not have to use this classes when handling every request. While authenticated user will have his roles "hide" in session tokens.
I am not sure if you can handle security problems only with two user classes.
And what about not authenticated users?
if you want to make security in your spring boot app you could use spring securtiy which handles roles and make it easy for you , you could also create your own succes handlers and failure handlers classes :
first you add these lines on your pom.xml under dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
you have to be able to manipulate thymleaf for login custom forms wich is not complicated ,after defining thymleaf and spring security you need to define a configuration class and data source like i did in the code below
import org.springframework.beans.factory.annotation.Autowired;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import javax.sql.DataSource ;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void GlobalConfig(AuthenticationManagerBuilder auth,DataSource dataSource) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
//this is actually where your roles and your user class interferes to login with different roles take this as exemple to your own need
.usersByUsernameQuery("SELECT login AS principal , password AS credentials , true FROM utilisateur WHERE login = ? ")
.authoritiesByUsernameQuery("SELECT u.login AS principal , r.role as role FROM utilisateur u ,role r where u.id_role=r.id_role AND login = ? ");
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/angular/**","/css/**","/js/**","/assets/**");
//this line to make full access of assets
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/yourHomePages/ThatDoesn'tIncludeLogin") .permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.logoutUrl("/logout")
.permitAll();
}
}
after defining the configuration class and data sources you need to define \login so you have to resolve the path by making MvcResolver like this
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
public class WebMVCconfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
viewName is you custom login that you'll create and it has to be under src/main/resources/templates/
for any further informations you could use these links
http://www.thymeleaf.org/doc/articles/springsecurity.html ,https://github.com/thymeleaf/thymeleafexamples-springsecurity and also spring security official documentation
Related
I am trying to create a login mechanism where in the login page I accept these 3 fields-
org_name, username, password.
While I have successfully been able to login using just the username and password combination, I need to check the combination of org_name & username, which is like a unique key in the users table.
I have explored Spring-boot's JDBC authentication, JPA authentication but just couldn't get it right.
For JDBC authentication based custom query, I am not able to fetch the 'org_name' parameter to be passed in the custom query.
P.S.: I am new to Springboot framework, so excuse me if the question is too noob. But I kinda need urget help on this.
Add the following dependency to the pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Then set up a WebSecurityConfig.java class
package com.example.securingweb;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
This was the general code I used while learning the spring security.
Thank you
I guess you need to do a little more work here. for username/password based authentication spring security use UsernamePasswordAuthenticationFilter. so you have to extend this filter and override the attemptAuthentication method where you will have access to the http request object.here you will have access to your extra 'org_name' parameter. then you need to implement the 'Authentication' interface where you will put your extra parameter. then from attemptAuthentication method you need to return an instance of your custom authentication object. you also need to provide your own authentication provider which will use this authentication object to authenticate the user.
I want to create a page in Spring, which has the url
http://myapp.com/sign-in?email=myemail#provider.com&pw=password
password is a one-time password that the user receives via e-mail every time they want to sign in.
Whenever the user visits this page, I want two things to happen:
Check whether or not the provided credentials are correct.
If they are, display the HTML content of the page.
I've done the first part:
#Autowired
private var userRepository: UserRepository? = null
#GetMapping
fun signIn(#RequestParam email:String, #RequestParam(name="pw") password:String): RedirectView {
// Is the password correct?
// TODO: Read password hash of the user
val existingUser: Optional<UserInfo>? = userRepository?.findById(email)
if (existingUser == null) {
return redirectToErrorPage("Please register with your e-mail address first")
}
if (!existingUser.isPresent) {
return redirectToErrorPage("Please register with your e-mail address first")
}
val hashInDb = existingUser.get().passwordHash
val hashInParam = PasswordHashCalculator.calculateHash(password)
if (!hashInDb.equals(hashInParam)) {
return redirectToErrorPage("Invalid user name and/or password")
}
// TODO: Display the main page
return null
}
How do I need to change the code in order to display the main page (HTML file in src/main/resources/static), but only if the authentication checks are passed?
Update 1: Using return ClassPathResource("main.html") as recommended here did not help.
return ClassPathResource("static/main.html") should answer your question, don't forget to specify `static` folder at the beginning as `ClassPathResource` points to the `resources` folder
You shouldn't really do your security this way. It's is not good practice to send your password in clear text over http.
There is examples here of basic authentication with spring security.
https://www.baeldung.com/spring-security-basic-authentication
https://www.baeldung.com/securing-a-restful-web-service-with-spring-security
What you can do then is, if you follow this tutorial, is assign an in memory user for starters. Then you can Base64Encode your authentication details to the user. Then for each user you can send the authentication details and no one can snoop the username and password as they pass along the wire and your request is dealt with before ever reaching your controller. This way, you can decouple your business logic from your authentication.
That's a start at least. Hope this helps.
#RobScully is right, you should not handle authorization like this . What you can do is enable spring-security and use spring security annotations to handle this scenario.
Use the following dependencies and setup a basic spring security setup.
Annotations like #PreAuthorize() can then be used to validate the user privileges before executing a method. You can even add this annotation to controller methods to validate before serving each request if you insist.
You can setup and LDAP server or Oauth or even use database for authentication (if you working on a demo or something).
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>
Use a configuration class like below to configure the security :
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select username,password, enabled from users where username=?")
.authoritiesByUsernameQuery(
"select username, role from user_roles where username=?");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')") //To check admin role permission
.and()
.formLogin().loginPage("/login").failureUrl("/login?error") //provide failure url
.usernameParameter("username").passwordParameter("password")
.and()
.logout().logoutSuccessUrl("/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();
}
}
This sample project in github gives a basic setup where you can use spring security:
https://github.com/mohchi/spring-security-request-mapping
Reference use:
https://www.mkyong.com/spring-security/spring-security-form-login-using-database/
Helllo, I'm using RESTful with basic authentication and this code is a part from the RestController:
#GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(#PathVariable String username) {
userId = getUserIdFromUsername(username);
return goalJpaRepository.findByUserId(userId);
}
public Long getUserIdFromUsername(String username) {
User user = userJpaRepository.findByUsername(username);
userId = user.getId();
return userId;
}
And I have a problem, for example I'm using Postman to retrieve the goals for a speciffic user like this:
http://localhost:8080/jpa/users/john/goals with GET request
Then I use the basic authentication for the username john and the password for this username and I receive the goals for john.
After that if I do a GET request for this link http://localhost:8080/jpa/users/tom/goals I receive the goals for tom, but I'm logged in with john at this moment of time, so john can see his goals and also he can see tom's goals.
The question is how can I access the login username in the RestController, because I want to do something like this:
if (loginUsername == username) {
return goalJpaRepository.findByUserId(userId);
}
return "Access denied!";
So I want to know if it is possible to access the login username from HTTP Header?
Thank you!
UPDATE - Yes the framework is Spring Boot, also I'm using Spring Security with Dao Authentication because I want to get the user from a MySQL database. Anyway I'm not an expert at Spring Security.
Now I understand how to use Principal in my controller methods, but I don't know how to use Spring Security for this specific case. How should I implement it? For example the user john should see and modify only his goals.
Spring Security Configuration:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.dgs.restful.webservices.goaltrackerservice.user.MyUserDetailsService;
#Configuration
#EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
private MyUserDetailsService userDetailsService;
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(bCryptPasswordEncoder());
return authProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/allusers").permitAll()
.anyRequest().authenticated()
.and()
// .formLogin().and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
}
Assuming you are using Spring as your Java framework, you should use Spring security to configure the basic authentication. Many tutorials available online (https://www.baeldung.com/spring-security-basic-authentication,
Spring Security will then provide a security context available throughout the app (SecurityContextHolder.getContext()) from which you can retrieve the connected user information (username, ...).
For instance to retrieve the username of the connected user, you should do :
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String loginUsername = authentication.getName();
Alternatively, as mentioned by #gervais.b Spring can inject the Principal (or Authentication) in your controller methods.
As said by #Glains An even better alternative is to use the #PreAuthorize and #PostAuthorize annotations, which alows you to define simple rules based on Spring Expression Language.
Please note that you are not doing any security at this time.
As said by #Matt "It depends which framework you are using". But I guess you are using spring. You should then have a look at the spring-securuty module documentation.
Basically you can inject the authenticated user into your method parameter :
#GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(#PathVariable String username, Principal principal) {
if ( username.equals(principal.getName()) ) {
userId = getUserIdFromUsername(username);
return goalJpaRepository.findByUserId(userId);
} else {
throw new SomeExceptionThatWillBeMapped();
}
}
But spring-security and many frameworks provide better patterns to manage the security.
You can also solve this problem with #PreAuthorize, an annotation offered by the Spring Security Framework, which uses the Spring Expression Language.
#PreAuthorize("principal.name == #username")
#GetMapping("/jpa/users/{username}/goals")
public List<Goal> getAllGoals(#PathVariable String username) {
return goalJpaRepository.findByUserId(userId);
}
Behind the scenes Spring will use the already mentioned SecurityContextHolder to fetch the currently authenticated principal. If the expression resolves to false, the response code 403 will be returned.
Please note that you have to enable global method security:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
To answer your new question on "Dao Authentication" the answer is to provide a custom UserDetailsService.
From the configuration that you have attached to your question it looks that you already have a MyUserDetailsService.
There are plenty of articles that explain how to use a custom DetailsService. This one seems to match your requirements : https://www.baeldung.com/spring-security-authentication-with-a-database
Edit: On how to be sure that only John can see John's items.
Basically, the only ting that you can do to esnure that only John can see his goals it to restrict the goals to only those owned by John. But there is plenty way of doing this.
As you suggest in your initial question, you can just select the goals for a specific user. The power with spring-security is that it can inject a Principal but also kind of orher authentication object.
You can also make that more implicit an filter a the DAO/Repository side by using the SecurityContextHolder. This approach is fine and looks better when your system is more user centric or like a multi-tenant system.
Using some specific #Annotations or Aspects would also be a solution but maybe less obvious in this case.
I am using spring boot security to authenticate and authorize my rest http apis.
I see that we can set authorization setttings like this
import org.springframework.context.annotation.Configuration;
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;
#EnableWebSecurity
#Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// .anyRequest().permitAll()
.antMatchers("/user/health_check/*").permitAll()
.antMatchers("/user/registration").permitAll()
.antMatchers("/user/login","/user/logout").hasAnyRole("USER","ADMIN","MANAGER")
.antMatchers("/admin/*").fullyAuthenticated()
.and().httpBasic()
.and().csrf().disable();
}
}
I wanted to know how to give different permission to different urls which differ in request methods?
Eg:
if i have to two urls like
// GET /api/account/{id}
// POST /api/account/{id}
// PUT /api/account/{id}
i wish to give only admin acces to PUT and user access to GET and both user and admin access to POST.. how do i achieve this?
You can pass request method in antMatchers.
Try with this:
http
.httpBasic().and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/account/**").hasRole("ADMIN")
.antMatchers(HttpMethod.POST, "/api/account/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/api/account/**").hasRole("ADMIN");
You can use #PreAuthorize annotation on controller's methods:
#GetMapping("/api/account/{id}")
#PreAuthorize("hasAutority('readAccountById')")
public Account getAccount(#PathVariable Integer id){
...
}
It works with Spring security context, and you can check user roles, authorities and many more.
For reference have a look at this article https://www.baeldung.com/spring-security-method-security
I've setup a spring-boot + spring-mvc + spring-security project.
Everything work as expected right now except for the invalid urls.
If I issue:
http://siteaddr.com/invalid/url
I expect to see my 404 not found page, but spring-security redirects to login page first and after authentication shows 404 not found!
I don't think this is how it should work!
Here is my Websecurity config:
package com.webitalkie.webapp.config;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import com.webitalkie.webapp.security.DatabaseUserDetailsServic;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DatabaseUserDetailsServic userDetailsService;
#Autowired
private ServletContext servletContext;
#Override
protected void configure(HttpSecurity http) throws Exception {
servletContext.getFilterRegistration(AbstractSecurityWebApplicationInitializer
.DEFAULT_FILTER_NAME).addMappingForUrlPatterns(EnumSet
.allOf(DispatcherType.class), false, "/*");
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/home/", "/home", "/home/index", "/", "/favicon.ico").permitAll()
.antMatchers("/contents/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/home/loginsec").failureUrl("/loginsecerror").permitAll()
.and()
.logout()
.logoutUrl("/home/logout")
.logoutSuccessUrl("/home/index")
.invalidateHttpSession(true);
}
#Override
#org.springframework.beans.factory.annotation.Autowired
protected void configure(
org.springframework.security.config.annotation
.authentication.builders.AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}
public PasswordEncoder getPasswordEncoder() {
return new BcryptPasswordEncoder(11);
}
}
Do you guys have any idea?
To customize your particular use case apply the inverted logic as suggested. You could do like this:
1) Replace
.anyRequest().authenticated()
by
.anyRequest().anonymous()
2) Add
.antMatchers("/protected-urls/**").authenticated()
The rule in 2) must come before that in 1) as the first match applies. Unless you have a common url prefix for protected resources you'll have to declare all the authenticated urls one by one.
You can also apply additional configuration overriding the
public void configure(WebSecurity web)...
for example to ignore static resources:
web.ignoring().antMatchers("/favicon.ico", "*.css")
Hope that helps.
This is a security feature, not a problem.
Your security model is "deny all unless explicitly allowed". If a request path is protected (i.e. doesn't match an explicitly permitAll path), then you would not want to reveal that it does not exist until the user was authenticated. In certain situations the 404 could leak private information
.../user/jones is 404? Hmm... something happened to Jones
This is the reason well designed login forms don't tell you "user not found" or "invalid password", and instead just say "invalid credentials" in all failure cases to avoid giving away too much.
The only way to get invalid URLs to bypass security would be to invert your security model, making everything public unless explicitly protected ("allow unless explicitly prohibited"). Which has its own set of issues, such as having to remember to update the definition every time a new root path is created.