Merge branch '16-setup-token-based-authentification' into 'master'
Resolve "Setup token based authentification" Closes #16 See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!31
This commit is contained in:
commit
1eb2892d17
@ -15,11 +15,18 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
implementation 'org.mariadb.jdbc:mariadb-java-client'
|
||||
|
||||
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||
implementation "com.auth0:java-jwt:3.10.2"
|
||||
|
||||
runtimeOnly 'com.h2database:h2'
|
||||
|
||||
testImplementation('org.springframework.boot:spring-boot-starter-test') {
|
||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ package de.hft.geotime;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = "de.hft")
|
||||
@ -12,4 +14,9 @@ public class GeotimeApplication {
|
||||
SpringApplication.run(GeotimeApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder bCryptPasswordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
package de.hft.geotime.security;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.hft.geotime.user.TimetrackUser;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import static com.auth0.jwt.algorithms.Algorithm.HMAC512;
|
||||
import static de.hft.geotime.security.SecurityConstants.*;
|
||||
|
||||
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(
|
||||
HttpServletRequest req,
|
||||
HttpServletResponse res) throws AuthenticationException {
|
||||
try {
|
||||
TimetrackUser creds = new ObjectMapper().readValue(req.getInputStream(), TimetrackUser.class);
|
||||
return authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
creds.getUsername(),
|
||||
creds.getPassword(),
|
||||
new ArrayList<>()
|
||||
)
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void successfulAuthentication(
|
||||
HttpServletRequest req,
|
||||
HttpServletResponse res,
|
||||
FilterChain chain,
|
||||
Authentication auth) {
|
||||
String token = JWT.create()
|
||||
.withSubject(((User) auth.getPrincipal()).getUsername())
|
||||
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
|
||||
.sign(HMAC512(SECRET.getBytes()));
|
||||
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package de.hft.geotime.security;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static de.hft.geotime.security.SecurityConstants.*;
|
||||
|
||||
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
|
||||
|
||||
public JWTAuthorizationFilter(AuthenticationManager authManager) {
|
||||
super(authManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||
String header = req.getHeader(HEADER_STRING);
|
||||
|
||||
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
|
||||
chain.doFilter(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
|
||||
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
|
||||
String token = request.getHeader(HEADER_STRING);
|
||||
if (token != null) {
|
||||
// parse the token.
|
||||
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
|
||||
.build()
|
||||
.verify(token.replace(TOKEN_PREFIX, ""))
|
||||
.getSubject();
|
||||
|
||||
if (user != null) {
|
||||
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package de.hft.geotime.security;
|
||||
|
||||
public class SecurityConstants {
|
||||
public static final String SECRET = "SecretKeyToGenJWTs";
|
||||
public static final long EXPIRATION_TIME = 864_000_000; // 10 days
|
||||
public static final String TOKEN_PREFIX = "Bearer ";
|
||||
public static final String HEADER_STRING = "Authorization";
|
||||
public static final String SIGN_UP_URL = "/user/sign-up";
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package de.hft.geotime.security;
|
||||
|
||||
import de.hft.geotime.user.UserDetailsServiceImpl;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpMethod;
|
||||
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.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import static de.hft.geotime.security.SecurityConstants.SIGN_UP_URL;
|
||||
|
||||
@EnableWebSecurity
|
||||
public class WebSecurity extends WebSecurityConfigurerAdapter {
|
||||
private final UserDetailsServiceImpl userDetailsService;
|
||||
private final BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||
|
||||
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.cors().and().csrf().disable().authorizeRequests()
|
||||
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
|
||||
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
|
||||
// this disables session creation on Spring Security
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
|
||||
return source;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package de.hft.geotime.timetrackaccount;
|
||||
|
||||
import de.hft.geotime.user.User;
|
||||
import de.hft.geotime.user.TimetrackUser;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@ -17,7 +17,7 @@ public class TimetrackAccount {
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private long id;
|
||||
@OneToOne
|
||||
private User user;
|
||||
private TimetrackUser timetrackUser;
|
||||
private double revenue;
|
||||
private String name;
|
||||
private String description;
|
||||
|
@ -5,6 +5,7 @@ import de.hft.geotime.timetrackaccount.TimetrackAccount;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.UniqueElements;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.List;
|
||||
@ -13,16 +14,18 @@ import java.util.List;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Entity
|
||||
public class User {
|
||||
public class TimetrackUser {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private long id;
|
||||
@UniqueElements
|
||||
private String username;
|
||||
private String password;
|
||||
private String firstname;
|
||||
private String lastname;
|
||||
@OneToMany
|
||||
private List<Role> roles;
|
||||
@OneToOne
|
||||
private Role role;
|
||||
@OneToMany
|
||||
private List<TimetrackAccount> timetrackAccounts;
|
||||
|
@ -0,0 +1,11 @@
|
||||
package de.hft.geotime.user;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import javax.websocket.server.PathParam;
|
||||
|
||||
public interface TimetrackUserRepository extends CrudRepository<TimetrackUser, Long> {
|
||||
|
||||
TimetrackUser findFirstByUsername(@PathParam("username") String username);
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package de.hft.geotime.user;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
public class UserController {
|
||||
|
||||
private TimetrackUserRepository userRepository;
|
||||
private BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||
|
||||
public UserController(TimetrackUserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
|
||||
this.userRepository = userRepository;
|
||||
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String getUsername(Authentication authentication) {
|
||||
TimetrackUser timetrackUser = userRepository.findFirstByUsername(authentication.getName());
|
||||
return "Welcome back " + timetrackUser.getFirstname() + " " + timetrackUser.getLastname();
|
||||
}
|
||||
|
||||
// TODO: implement register, maybe move to another class
|
||||
@PostMapping("/sign-up")
|
||||
public HashMap<String, Object> signUp(@RequestBody HashMap<String, Object> payload) {
|
||||
return payload;
|
||||
// user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
|
||||
// userRepository.save(user);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package de.hft.geotime.user;
|
||||
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@Service
|
||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
private final TimetrackUserRepository userRepository;
|
||||
|
||||
public UserDetailsServiceImpl(TimetrackUserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
TimetrackUser timetrackUser = userRepository.findFirstByUsername(username);
|
||||
if (timetrackUser == null) {
|
||||
throw new UsernameNotFoundException(username);
|
||||
}
|
||||
System.out.println("Loaded user " + timetrackUser.getFirstname() + " " + timetrackUser.getLastname());
|
||||
return new User(timetrackUser.getUsername(), timetrackUser.getPassword(), Collections.emptyList());
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package de.hft.geotime.user;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import javax.websocket.server.PathParam;
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRepository extends CrudRepository<User, Long> {
|
||||
|
||||
List<User> findByUsername(@PathParam("username") String username);
|
||||
|
||||
}
|
@ -2,4 +2,5 @@ spring.jpa.hibernate.ddl-auto=update
|
||||
spring.datasource.url=jdbc:mariadb://db:3306/geotime
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=supersecure
|
||||
spring.datasource.initialization-mode=always
|
||||
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
@ -1,4 +1,5 @@
|
||||
server.port=80
|
||||
spring.datasource.hikari.initialization-fail-timeout=0
|
||||
spring.datasource.hikari.max-lifetime=300000
|
||||
spring.jpa.show-sql=true
|
||||
spring.profiles.active=prod
|
18
backend/src/main/resources/data.sql
Normal file
18
backend/src/main/resources/data.sql
Normal file
@ -0,0 +1,18 @@
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
DELETE FROM timetrack_user;
|
||||
DELETE FROM role;
|
||||
|
||||
INSERT INTO role (id, `name`) VALUES
|
||||
(1, 'Admin');
|
||||
|
||||
/* password is the firstname in lowercase e.g. marcel or tobias
|
||||
https://bcrypt-generator.com/ with 10 rounds
|
||||
*/
|
||||
INSERT INTO timetrack_user (id, firstname, lastname, password, username, role_id) VALUES
|
||||
(1, 'Marcel', 'Schwarz' ,'$2y$10$pDBv7dEaAiNs5Kr1.8g4XuTFx48zGxJu77rei4TlO.sDOF2yHWxo.', 'scma', 1),
|
||||
(2, 'Tobias', 'Wieck' ,'$2y$10$Fxj5cGrZblGKjIExvS/MquEE0lgyYo1ILxPgPR2vSiaaLKkqJ.C.u', 'wito', 1),
|
||||
(3, 'Tim', 'Zieger' ,'$2y$10$pYGHZhoaelceImO7aIN4nOkWJBp.oqNGFYaRAonHkYF4u9ljqPelC', 'ziti', 1),
|
||||
(4, 'Simon', 'Kellner' ,'$2y$10$Puzm/Nr/Dyq3nQxlkXGIfubS5JPtXJSOf2e6mrQ6HhVYQN9YiQQsC', 'kesi', 1);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
Loading…
Reference in New Issue
Block a user