VULPES TRIBES BACKEND
OUR TEAM PROJECT AT GFA/HÚLI
INTRODUCTION OF TEAM MEMBERS
JIŘINA KOPSOVÁ
• https://github.com/Kopsova/
• www.linkedin.com/in/jirinakopsova
PETER KOŽUCH
• https://github.com/PeterXMR
• www.linkedin.com/in/PeterKozuch
VOJTĚCH BLUDSKÝ
• https://github.com/Organt
• www.linkedin.com/in/vojtech-
bludsky
JIŘÍ SOUŠEK
• https://github.com/jsousek
• https://www.linkedin.com/in/jirisous
ek/
ABOUT OUR PROJECT
• WE ARE JAVA BACKEND GROUP, CREATING A
RESTFUL APPLICATION USING JAVA SPRING BOOT
FRAMEWORK
• WE ARE CURRENTLY WORKING ON WEB
APPLICATION SIMILAR TO TRAVIAN GAME,
WHICH IS AN MMO STRATEGIC GAME
• FRONTENT IS TASK FOR ANOTHER GROUP. BUT
VE CAN SEND COOL JSONS 
HOW IT WORKS
• A KINGDOM IN OUR GAME IS MANAGED BY THE
USER, WHO CAN BUILD DIFFERENT TYPES OF
BUILDINGS.
• BUILDINGS CAN BE FURTHER DEVELOPED IN
ORDER TO MAKE THEM MORE PRODUCTIVE.
• ALSO, THE USER BUILDS ARMIES TO FIGHT OTHER
PLAYERS.
LIST OF TECHNOLOGIES USED IN THE
PROJECT
• SPRING BOOT
• HIBERNATE
• ORACLE MYSQL
• FLYWAY DB MIGRATION
• JWT TOKEN AUTHENTICATION
• BCRYPT PW ENCRYPTION
• JUNIT
• MOCKITO TEST FRAMEWORK
• MOCK MVC
• JENKINS CI
• HEROKU DEPLOYMENT
• POSTMAN
WHERE TO FIND OUR APP
URL:
https://vulpes-tribes-backend.herokuapp.com
GitHub Repository:
https://github.com/green-fox-academy/vulpes-
tribes-backend
WHAT ARE WE GOING TO TELL YOU ABOUT IN
FURTHER DETAIL
• SECURITY IS HANDLED BY JWT TOKENS, PASSWORDS ARE ENCRYPTED USING
BCRYPT. MORE ON THE TOPIC LATER BY JIŘKA.
• TESTING IS DONE BY JUNIT AND MOCKITO. VOJTA WILL TELL YOU MORE.
• THE PERSISTENCE LAYER IS MANAGED BY MYSQL, DATABASE VERSIONING AND
MIGRATION IS BEING DONE BY FLYWAY. (PICTURE OF FLYWAY ROOT FOLDER
FROM INTELLIJ). PETER WILL TELL YOU MORE.
• THE APPLICATION IS DEPLOYED ON HEROKU USING JENKINS CONTINUOUS
DELIVERY. I AM GOING TO TELL YOU LATER.
JWT TOKEN
AUTHENTICATION
JIŘINA KOPSOVA
public class SecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/kingdom/**", "/user/**").authenticated()
.antMatchers("/").permitAll()
.and()
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.exceptionHandling().authenticationEntryPoint(forbiddenExceptionHandler);
}
{
"username": "adamgyulavari",
"password": "abc123"
}
Player loggs in with username and password:
} else if
(userTRepository.findTribesUserByUsername(tribesUser.getUsername()).get().getPasswo
rd().equals(tribesUser.getPassword())) {
TribesUser user =
userTRepository.findTribesUserByUsername(tribesUser.getUsername()).get();
user.setLoggedIn(true);
userTRepository.save(user);
return new ResponseEntity(
new
OKstatus(JWTService.createToken(tribesUser.getUsername())),HttpStatus.OK);
@PostMapping(value = "/login")
public ResponseEntity loginUser(@RequestBody TribesUser tribesUser) {
UserRestController
public class JWTService {
public static String createToken (String username){
String jwtToken = JWT.create()
.withSubject(username)
.withExpiresAt(new
Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(HMAC512(SecurityConstants.SECRET.getBytes()))
;
return jwtToken;
}
Method createToken
USER IS LOGGED IN SUCCESSFULLY.
JWT TOKEN IS CREATED.
{ "status": "ok",
"tribes_token":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZGFtZ3l1bGF2YXJpIiwiZX
hwIjoxNTQ5Mjc2NzQ1fQ.xYiud2OUo8TycTUt3nueW-
ULDEDA_sAc_fyi0joMUjb2uUF_1SfvhoC7zbU9uWNWHHuPO-zbxXiY1BkWJQ-
fmg "
}
To access secured endpoint, JWT token must be present in the header of each
HTTP request.
SECURITY IS HANDLED BY
JWTAUTHORISATIONFILTER
IT IS NOT NECESSARY TO SECURE EVERY
SINGLE ENDPOINT SEPARATELY.
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) {
chain.doFilter(req, res);
return;
}
try {
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}catch (JWTDecodeException e ){
JWTService.invalidTokenResponce(res);
}
}
If Header is missing, it is handled in
ForbiddenExceptionHandler Class
@Component
public class ForbiddenExceptionHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException
) throws IOException, ServletException {
Authentication auth
= SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
System.out.println("User: " + auth.getName()
+ " attempted to access the protected URL: "
+ request.getRequestURI());
}
JWTService.invalidTokenResponce(response);
}
}
Header is present, but Token is not valid
- handled in JWTAuthorisationFilter
public class JWTService {
public static void invalidTokenResponce (HttpServletResponse response) {
response.setStatus(403);
PrintWriter out = null;
try {
out = response.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = null;
try {
jsonString = objectMapper.writeValueAsString(new ErrorResponseModel("Invalid token"));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
out.print(jsonString);
out.flush();
}
METHOD invalidTokenResponse
{
"status": "error",
"message": "Invalid
token"
}
Invalid token or missing header
UNIT TESTING
VOJTĚCH BLUDSKÝ
UNIT TESTING
UserRestControllerTest
@RunWith(SpringJUnit4ClassRunner.class)
public class UserRestControllerTest {
@Mock
private UserTRepository userTRepository;
@Mock
BCryptPasswordEncoder bCryptPasswordEncoder;
@InjectMocks
private UserRestController userRestController;
private MockMvc mockMvc;
@Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(userRestController).build();
}
UserRestControllerTest
@Test
public void testLoginWrongPassword() throws Exception {
TribesUser registeredUser = new TribesUser("Smoula", "12345678ab");
TribesUser wrongPasswordUser = new TribesUser("Smoula", "differentPassword");
Mockito.when(userTRepository.findTribesUserByUsername(wrongPasswordUser.getUsername())).thenReturn(Optional.of(registeredUser));
Mockito.when(bCryptPasswordEncoder.matches(wrongPasswordUser.getPassword(), registeredUser.getPassword())).thenReturn(false);
mockMvc.perform(MockMvcRequestBuilders.post("/login")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(asJsonString(wrongPasswordUser))
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(MockMvcResultMatchers.jsonPath("$.status", Matchers.is("error")))
.andExpect(MockMvcResultMatchers.jsonPath("$.message", Matchers.is("Wrong password!")));
Mockito.verify(userTRepository, Mockito.atLeast(2)).findTribesUserByUsername(registeredUser.getUsername());
Mockito.verify(bCryptPasswordEncoder, Mockito.atLeast(1)).matches("differentPassword", "12345678ab");
}
UserRestControllerTest
TribesUser registeredUser = new TribesUser("Smoula", "12345678ab");
TribesUser wrongPasswordUser = new TribesUser("Smoula", “wrongPassword");
UserRestControllerTest
Mockito.when(userTRepository.findTribesUserByUsername(wrongPasswordUser
.getUsername())).thenReturn(Optional.of(registeredUser));
Mockito.when(bCryptPasswordEncoder.matches(wrongPasswordUser.getPassword(),
registeredUser.getPassword())).thenReturn(false);
UserRestControllerTest
mockMvc.perform(MockMvcRequestBuilders.post("/login")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(asJsonString(wrongPasswordUser))
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(MockMvcResultMatchers.jsonPath("$.status",
Matchers.is("error")))
.andExpect(MockMvcResultMatchers.jsonPath("$.message",
Matchers.is("Wrong password!")));
UserRestControllerTest
public static String asJsonString(final TribesUser user) {
try {
final ObjectMapper mapper = new ObjectMapper();
final String jsonContent = mapper.writeValueAsString(user);
return jsonContent;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
UserRestControllerTest
Mockito.verify(userTRepository,
Mockito.atLeast(2)).findTribesUserByUsername
(registeredUser.getUsername());
Mockito.verify(bCryptPasswordEncoder,
Mockito.atLeast(1)).matches("differentPassword", "12345678ab");
DATABASE MIGRATION
PETER KOŽUCH
WE ARE USING
FLYWAY
• TO HANDLE OUR
DATABASES MIGRATIONS
WHY WE HAVE DATABASE MIGRATIONS IN
OUR APP
• WE CAN HAVE SEVERAL VERSIONS OF OUR DATABASE
• WE CAN FILL DATABASE WITH DATA IN A CONTROLLED MANNER
• WE DO NOT NEED DROP DATABASE AT HEROKU IF DATABASE
STRUCTURE HAS BEEN CHANGED
FLYWAY SCHEMA HISTORY
CONTINIOUS INTEGRATION
IMPLEMENTATION
JIŘÍ SOUŠEK
PURPOSE OF CONTINIOUS INTEGRATION
• TO DEPLOY AN APPLICATION TO A SERVER WITHOUT AN UNNECESSARY NEED
FOR HUMAN ATTENTION
• APP DEPLOYS AUTOMATICALLY WITH EVERY COMMIT TO CERTAIN GITHUB
BRANCH
• APP IS COMPILED AND TESTED USING JENKINS
• IF SUCCESSFULLY COMPILED AND TESTED, JENKINS PUSH APP TO HEROKU
• IN THE CASE OF ERROR, A LOG IS STORED ON JENKINS
GitHub webhook is created
Jenkins repositories for pull and push
App build and a new branch creation and
checkout in Jenkins
Post build push to heroku using git publisher
Local variables settins in Heroku

Vulpes tribes backend