Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6 database authentication provider #3

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
79 changes: 79 additions & 0 deletions src/main/java/guru/sfg/brewery/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package guru.sfg.brewery.config;

import guru.sfg.brewery.filter.RestHeaderAuthFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

public RestHeaderAuthFilter restHeaderAuthFilter(AuthenticationManager authenticationManager){
RestHeaderAuthFilter restHeaderAuthFilter = new RestHeaderAuthFilter(new AntPathRequestMatcher("/api/**"));
restHeaderAuthFilter.setAuthenticationManager(authenticationManager);
return restHeaderAuthFilter;
}
// By default, Spring Security turns on csrf protection.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(restHeaderAuthFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
http
.authorizeRequests(authorize -> {
authorize
.antMatchers("/h2-console/**").permitAll() // do not use in production
.antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll()
.antMatchers("/beers/find", "/beers*").permitAll()
.antMatchers(HttpMethod.GET, "/api/v1/beer/**").permitAll()
.mvcMatchers(HttpMethod.GET, "/api/v1/beerUpc/{upc}").permitAll();
} )
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();

// h2 console config
http.headers().frameOptions().sameOrigin();
}

// Configuring In Memory Authentication using Authentication Fluent API.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("spring")
.password("guru")
.roles()
.and()
.withUser("user")
.password("{noop}password")
.roles("USER");

}

// This is kinda an old way of creating InMemory UserDetails Service. Alternatively, we can use
// In Memory Authentication Fluent API. See above
// @Override
// @Bean
// protected UserDetailsService userDetailsService() {
// UserDetails admin = User.withDefaultPasswordEncoder()
// .username("spring")
// .password("guru")
// .roles("ADMIN")
// .build();
// UserDetails user = User.withDefaultPasswordEncoder()
// .username("user")
// .password("password")
// .roles("USER")
// .build();
// // InMemoryUserDetailsManager implements UserDetailsService thus overriding default UserDetailsService
// return new InMemoryUserDetailsManager(admin, user);
// }
}
22 changes: 22 additions & 0 deletions src/main/java/guru/sfg/brewery/domain/security/Authority.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package guru.sfg.brewery.domain.security;

import lombok.*;

import javax.persistence.*;
import java.util.Set;

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Authority {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private String role;

@ManyToMany(mappedBy = "authorities")
private Set<User> users;
}
36 changes: 36 additions & 0 deletions src/main/java/guru/sfg/brewery/domain/security/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package guru.sfg.brewery.domain.security;

import lombok.*;

import javax.persistence.*;
import java.util.Set;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Builder
public class User {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String username;
private String password;
@Singular
@ManyToMany(cascade = CascadeType.MERGE)
@JoinTable(name = "user_authority",
joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")},
inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")})
private Set<Authority> authorities;
// @Builder.Default - для того, чтобы в @Builder можно было задать дефолтное значение
@Builder.Default
private Boolean accountNonExpired = true;
@Builder.Default
private Boolean accountNonLocked = true;
@Builder.Default
private Boolean credentialsNonExpired = true;
@Builder.Default
private Boolean enabled = true;
}
110 changes: 110 additions & 0 deletions src/main/java/guru/sfg/brewery/filter/RestHeaderAuthFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package guru.sfg.brewery.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* AbstractAuthenticationProcessingFilter - abstract processor of browser-based HTTP-based authentication requests.
* The filter requires that you set the authenticationManager property. An authenticationManager is required to process
* the authentication request tokens creating by implementing classes. This filter will intercept a request and attempt
* to perform authentication from that request if the request URL matches the value of the filterProcessesUrl property.
* This behaviour can be modified by overriding the method requiresAuthentication. Authentication is performed by the
* attemptAuthentication method, which must be implemented by subclasses.
*
* If authentication is successful, the resulting Authentication object will be placed into the SecurityContext
* for the current thread, which is guaranteed to have already been created by an earlier filter.
* */
@Slf4j
public class RestHeaderAuthFilter extends AbstractAuthenticationProcessingFilter {

public RestHeaderAuthFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}

@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {

String userName = getUsername(httpServletRequest);
String password = getPassword(httpServletRequest);

if (userName == null){
userName = "";
}
if (password == null){
password = "";
}
log.debug("Authenticating user: {}", userName);

// An Authentication implementation that is designed for simple presentation of a username and a password.
// Needed for work with authentication manager.
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password);
// AuthenticationManager is configured as InMemory in fluent api setting
if (!StringUtils.isEmpty(userName)){
return this.getAuthenticationManager().authenticate(token);
}
return null;
}

private String getPassword(HttpServletRequest httpServletRequest) {
return httpServletRequest.getHeader("Api-Secret");
}

private String getUsername(HttpServletRequest httpServletRequest) {
return httpServletRequest.getHeader("Api-Key");
}

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult = attemptAuthentication(request, response);
try {
if (authResult != null){
this.successfulAuthentication(request, response, chain, authResult);
}else {
chain.doFilter(request, response);
}
} catch (AuthenticationException e){
log.error("Authentication Failed");
unsuccessfulAuthentication(request, response, e);
}
}
}

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
}

protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (this.log.isDebugEnabled()) {
log.debug("Authentication request failed: " + failed.toString(), failed);
log.debug("Updated SecurityContextHolder to contain null Authentication");
}
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package guru.sfg.brewery.repositories.security;

import guru.sfg.brewery.domain.security.Authority;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AuthorityRepository extends JpaRepository<Authority, Integer> {


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package guru.sfg.brewery.repositories.security;

import guru.sfg.brewery.domain.security.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Integer> {

Optional<User> findByUsername(String username);
}
49 changes: 49 additions & 0 deletions src/main/java/guru/sfg/brewery/services/JpaUserDetailsService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package guru.sfg.brewery.services;

import guru.sfg.brewery.domain.security.Authority;
import guru.sfg.brewery.domain.security.User;
import guru.sfg.brewery.repositories.security.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class JpaUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

// We are using @Transactional for executing this method in one Hibernate context so that we do not need
// to configure user to load authorities eagerly (we could alternatively do that).
@Transactional
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username).orElseThrow(()-> {
throw new UsernameNotFoundException("User name: " + username + " not found");
});
// User из пакета org.springframework.security.core.userdetails.User implements UserDetails interface and is
// immutable.
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
user.getEnabled(), user.getAccountNonExpired(), user.getCredentialsNonExpired(), user.getAccountNonLocked(),
convertToSpringAuthorities(user.getAuthorities()));
}

private Collection<? extends GrantedAuthority> convertToSpringAuthorities(Set<Authority> authorities) {
if (authorities != null && authorities.size() > 0){
return authorities.stream().map(Authority::getRole).map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
}else {
return new HashSet<>();
}
}
}
Loading