Implementing JWT Authentication on Spring Boot APIs - rest

I have a SpringBoot 2.0.2.RELEASE web application, with this config file:
#Override
protected void configure(HttpSecurity http) throws Exception {
final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains("dev")) {
http.csrf().disable();
http.headers().frameOptions().disable();
}
http
.authorizeRequests()
.antMatchers(publicMatchers()).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
.failureUrl("/login?error").permitAll()
.and()
.logout().permitAll();
}
I want to add a Custom JWT based security filter ONLY for the Rest Controllers that will be under the match /rest/** , so I modified the config to this file, but now I can't log into the app, because I have a HTTP Status 401 – Unauthorized
#Override
protected void configure(HttpSecurity http) throws Exception {
final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains("dev")) {
http.csrf().disable();
http.headers().frameOptions().disable();
}
http
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(publicMatchers()).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
.failureUrl("/login?error").permitAll()
.and()
.logout().permitAll();
// Custom JWT based security filter
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
and the filter (that extends from OncePerRequestFilter )
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
logger.info("processing authentication for '{}'", request.getRequestURL());
if (request.getRequestURI().indexOf("/rest/")==-1) {
chain.doFilter(request, response);
return;
}
final String requestHeader = request.getHeader(this.tokenHeader);
String username = null;
String authToken = null;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
logger.info("an error occured during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.info("the token is expired and not valid anymore", e);
}
} else {
logger.info("couldn't find bearer string, will ignore the header");
}
logger.info("checking authentication for user '{}'", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
logger.info("security context was null, so authorizating user");
// It is not compelling necessary to load the use details from the database. You could also store the information
// in the token and read it from it. It's up to you ;)
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// For simple validation it is completely sufficient to just check the token integrity. You don't have to call
// the database compellingly. Again it's up to you ;)
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info("authorizated user '{}', setting security context", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
....
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return request.getRequestURI().indexOf("/rest/")==-1;
}
in the logger I see
("couldn't find bearer string, will ignore the header"
Because I only want to apply the JWT filter in the RestContollers not in all of them, like LoginController
With this config class I can access to the /rest/ URL only being logged in the app.
#Profile("web")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOG = LoggerFactory.getLogger(WebSecurityConfig.class);
#Autowired
private UserSecurityService userSecurityService;
#Value("${server.servlet.context-path}")
private String serverContextPath;
/** The encryption SALT. */
private static final String SALT = "fd&lkj§sfs23#$1*(_)nof";
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Configuration
#Order(1)
public static class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.header}")
private String tokenHeader;
#Value("${jwt.route.authentication.path}")
private String authenticationPath;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// we don't need CSRF because our token is invulnerable
.csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(“/rest/**”).permitAll().anyRequest().authenticated()
.antMatchers(“**/rest/**”).permitAll().anyRequest().authenticated();
// Custom JWT based security filter
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// disable page caching
http.headers().frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
.cacheControl();
}
}
#Configuration
#Order(0)
public static class OtherSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${server.servlet.context-path}")
private String serverContextPath;
#Autowired
private Environment env;
#Override
protected void configure(HttpSecurity http) throws Exception {
final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains("dev")) {
http.csrf().disable();
http.headers().frameOptions().disable();
}
http.authorizeRequests()
.antMatchers(publicMatchers())
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
.failureUrl("/login?error").permitAll()
.and()
.logout()
.permitAll();
}
private String[] publicMatchers() {
/** Public URLs. */
final String[] PUBLIC_MATCHERS = {
"/webjars/**",
serverContextPath + "/css/**",
serverContextPath + "/js/**",
serverContextPath + "/fonts/**",
serverContextPath + "/images/**",
serverContextPath ,
"/",
"/error/**/*",
"/console/**",
ForgotMyPasswordController.FORGOT_PASSWORD_URL_MAPPING,
ForgotMyPasswordController.CHANGE_PASSWORD_PATH,
SignupController.SIGNUP_URL_MAPPING
};
return PUBLIC_MATCHERS;
}
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
}
}

In a nutshell, you have two subpaths (namely /rest/** and others) in the same application, and you want to apply different login schemes for each. Spring-security allows you to have multiple configurations, allowing for such a scenario.
I would do something like this:
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class ApiSecurityConfiguration
extends WebSecurityConfigurerAdapter {
private final JwtAuthorizationTokenFilter jwtFilter = new ...
private final AuthenticationEntryPoint unauthorizedHandler = new ...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/rest/**").authorizeRequests()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.addFilter(jwtFilter);
}
}
#Configuration
public static class OtherSecurityConfiguration
extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/bonanza/list")
.failureUrl("/login?error").permitAll()
.and()
.logout().permitAll();
}
}
}
With such configuration, the JwtAuthorizationTokenFilter should only be activated for the matching paths. Thus I think you won't need to check for the paths in JwtAuthorizationTokenFilter.

Related

Spring boot api gives 403 forbidden error

I have a spring boot application with a mongo databse and spring security as a dependency.
It has two services first one for authentication and second one for application resource (entities, services controllers).
This is my config class in the authentication service:
#Configuration
#EnableWebSecurity
public class AuthServerSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
protected UserDetailsService userDetailsService() {
return new MongoUserDetailsService();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().anyRequest().authenticated();
System.out.println("auth");
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
#Bean(name="authenticationManager")
#Lazy
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
this is the rest controller:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping(value = "/api/users")
public class UserController {
#Autowired
UserServiceImpl userServiceImpl;
//Getting all users
#GetMapping(value = "")
public List<UserDTO> getAllUsers() {
return userServiceImpl.getAllUsers();
}
//Getting a user by ID
#GetMapping(value = "/profil/{userId}")
public UserDTO getUserById(#PathVariable String userId) {
return userServiceImpl.getUserById(userId);
}
//Getting a user by Username
#GetMapping(value = "/profil/username/{username}")
public UserDTO getUserByUsernameOrEmail(String username) {
return userServiceImpl.getUserByUsernameOrEmail(username);
}
//Logout user and delete token
#PostMapping("/logout")
public void logout(HttpServletRequest request) {
userServiceImpl.logout(request);
}
I changed my configure method to this :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests() // authorize
.anyRequest().authenticated() // all requests are authenticated
.and()
.httpBasic();
http.cors();
}
Now i get 401 unauthorized when acceccing protected resources.The problem is now even when i send the correct bearer token in the request header i still get 401 unauthorized "Full authentication is required to access this resource"
Update:
I changed my project architecture from microservices to one simple spring boot project.
this is the new code of the class "AuthServerSecurityConfig"
#Configuration
public class AuthServerSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
protected UserDetailsService userDetailsService() {
return new MongoUserDetailsService();
}
#Autowired
BCryptPasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(userDetailsService());
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll().and()
.httpBasic();
http.cors();
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
#Bean(name="authenticationManager")
#Lazy
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
and this "ResourceServerConfig" code:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired private ResourceServerTokenServices tokenServices;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("foo").tokenServices(tokenServices);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() // authorize
.antMatchers("/oauth/**").permitAll();
http
.authorizeRequests().antMatchers("/api/**").authenticated();
http
.headers().addHeaderWriter(new HeaderWriter() {
#Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
response.addHeader("Access-Control-Allow-Origin", "*");
if (request.getMethod().equals("OPTIONS")) {
response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
}
}
});
}
}
When i try to access protected resource i get "error": "unauthorized",
"error_description": "Full authentication is required to access this resource", Which is the normal behaviour.The problem is now i can't login to get the user aceess_token.
I get "401 unauthorized" when accessing this endpoint "http://localhost:8080/oauth/token?grant_type=password&username=user&password=user".
These are the default init user credentials and the user exists in my mongodatabase with a crypted and correct format password starts with "$2a" and has "60" caracters.
I get in "Encoded password does not look like BCrypt
Authentication failed: password does not match stored value" in the console when trying to login.
In ResourceServerConfig class file, in the configure method change to the below code.
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll().and()
.httpBasic();
Let me know if it worked.
Here's an example of configuration spring security with a jwt token check :
you can change the data source from h2 to mongodb and find the filters and providers used in my repo :
https://github.com/e2rabi/sbs-user-management/tree/main/sbs-user-management
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
#FieldDefaults(level = PRIVATE, makeFinal = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
// Put your public API here
new AntPathRequestMatcher("/public/**"),
new AntPathRequestMatcher("/h2-console/**"),
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
TokenAuthenticationProvider provider;
SecurityConfig(final TokenAuthenticationProvider provider) {
super();
this.provider = requireNonNull(provider);
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
#Override
public void configure(final WebSecurity web) {
web.ignoring().requestMatchers(PUBLIC_URLS);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.exceptionHandling()
// this entry point handles when you request a protected page and you are not yet
// authenticated
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.and()
.authenticationProvider(provider)
.addFilterBefore(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.csrf().disable()
.cors().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
// h2 console config
http.headers().frameOptions().sameOrigin();
// disable page caching
http.headers().cacheControl();
}
#Bean
TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy(new NoRedirectStrategy());
return successHandler;
}
#Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
FilterRegistrationBean disableAutoRegistration(final TokenAuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
}
Hope that my answer can help,
you can drop a breakpoint to the line change the response status, and then check who and why it returns 403, it can finally help you get the solution
Drop a breakpoint on the line set the 403 status, to see how this happen from the stackframes.
Guess that it returns 403 without much other information, but it must need to set the status to the response, right? So drop a breakpoint to the setStatus method, I don't know where it should locate, in tomcat lib, spring lib, or servlet lib. Check the HttpResponse, they're several implementation, set the breakpoints for those setStatus/setCode methods. (Next you can see it acutally happens at HttpResponseWrapper::setStatus)
Analyze the stackframes to see what's going on there
please check https://stackoverflow.com/a/73577697/4033979

Authorization code grant

I'm working on a REST API, that listens on localhost and I want to include Spring Security. Password grant and client credentials grant works perfectly and I can go check secured data from /smarthouse and /smarthouse2.
Although, when I try to use authorization code grant through postman, it gives me the same error and I have checked everywhere. My project is here: https://github.com/sharjak/Smarthouse. The action all happens in the demoapplication folder.
Authorization code in Postman
My code for authorization and resource server:
#Configuration
public class OAuth2ServerConfig {
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated().and()
.formLogin();
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("my-trusted-client")
.authorizedGrantTypes("password","authorization_code","refresh_token", "implicit")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT","ROLE_USER")
.scopes("read", "write", "trust")
.resourceIds("oauth2-resource")
.secret("secret")
.accessTokenValiditySeconds(6000)
.and()
.withClient("my-client")
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("ROLE_CLIENT", "ROLE_USER")
.scopes("read","trust", "write")
.resourceIds("oauth2-resource")
.accessTokenValiditySeconds(6000)
.and()
.withClient("my-client-with-secret")
.authorizedGrantTypes("client_credentials","password")
.authorities("ROLE_CLIENT", "ROLE_USER")
.scopes("read", "trust", "write")
.resourceIds("oauth2-resource")
.secret("secret")
.accessTokenValiditySeconds(6000);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("permitAll()");
}
}
}
Code for Websecurity server:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ClientDetailsService clientDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.csrf().disable()
.authorizeRequests()
.antMatchers("/smarthouse", "smarthouse2", "/user").permitAll()
.and()
.formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("password").roles("ADMIN")
.and()
.withUser("sander").password("Sander123").roles("USER");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
Stacktrace when I try to log in with a user:
org.springframework.security.authentication.InsufficientAuthenticationException: User must be authenticated with Spring Security before authorization can be completed.
at org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(AuthorizationEndpoint.java:138)
I'm quite a beginner, but it seems like a small problem to fix. Can anyone help me?
You just have to change your configure method from WebSecurityConfig to what follows:
http
.authorizeRequests()
.antMatchers("/login", "/favicon.ico",
"/oauth/confirm_access", "/oauth/token", "/smarthouse",
"smarthouse2", "/user").permitAll()
.anyRequest().authenticated().and()
.csrf().disable();
Why do you want to disable anonymous access?
Another point is that the order the matchers are declared matters.
I have cloned your repo and doing it worked for me.

No mapping found for HTTP request with URI

I have Spring Boot application, everything works fine until I implement spring security in front of my application. This is a RESTful api that has a token based authentication. What's even more weird it works (!) intermittently - by intermittently I mean restarting the application will return the right responses such as 401/403 if unauthenticated and other codes if user is authorized to access them. This is being deployed into WebLogic.
2017-01-05 14:12:51.164 WARN 11252 --- [ (self-tuning)'] o.s.web.servlet.PageNotFound : No mapping found for HTTP request with URI [/user] in DispatcherServlet with name 'dispatcherServlet'
WebApplication.java
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
public class WebApplication extends SpringBootServletInitializer implements WebApplicationInitializer {
public static void main(String[] args) {
Object[] sources = new Object[2];
sources[0] = WebConfiguration.class;
sources[1] = WebSecurityConfiguration.class;
SpringApplication.run(sources, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(WebApplication.class);
}
}
WebConfiguration.java
#Configuration
#ComponentScan(basePackages = { "com.controller", "com.service", "com.dao"})
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class })
public class WebConfiguration extends WebMvcConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(WebConfiguration.class);
/**
* Setup a simple strategy: use all the defaults and return XML by default
* when not sure.
*/
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON).mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
#Bean(name = "entityManagerFactory")
public EntityManagerFactory getQmsEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceUnitName(Config.PERSISTENCE_UNIT_NAME);
em.setPersistenceXmlLocation("META-INF/persistence.xml");
em.setDataSource(getDataSource());
em.setJpaVendorAdapter(getJpaHibernateVendorAdapter());
em.afterPropertiesSet();
return em.getObject();
}
#Bean
public HibernateJpaVendorAdapter getJpaHibernateVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(true);
// adapter.setDatabase("ORACLE");
adapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");
return adapter;
}
#Bean(name="dataSource", destroyMethod = "")
//http://stackoverflow.com/questions/19158837/weblogic-datasource-disappears-from-jndi-tree
#Qualifier("dataSource")
#Profile("weblogic")
public DataSource dataSource() {
DataSource dataSource = null;
JndiTemplate jndi = new JndiTemplate();
try {
dataSource = (DataSource) jndi.lookup("jdbc/datasource");
} catch (NamingException e) {
logger.error("NamingException for jdbc/datasource", e);
}
return dataSource;
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("*");
}
};
}
}
WebSecurityConfiguration.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan({
"com.subject",
"com.custom"
})
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private StatelessAuthenticationFilter statelessAuthenticationFilter;
#Autowired
private RestAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private CusAuthenticationProvider cusAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(cusAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.securityContext()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.addFilterBefore(statelessAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler);
}
}
StatelessAuthenticationFilter.java
#Component
public class StatelessAuthenticationFilter extends OncePerRequestFilter {
#Inject
private SubjectLookupService subjectLookupService;
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authenticateUser(request));
filterChain.doFilter(request, response);
}
private Authentication authenticateUser(HttpServletRequest request) {
try {
String application = StringUtils.defaultString(request.getParameter("application"));
UserInfo me = subjectLookupService.getUserInfo();
List<GrantedAuthority> roles = me.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())).collect(Collectors.toList());
UserDetails user = new User(me.getUsername(), "", roles);
Authentication authentication = new UserAuthentication(user);
return authentication;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Controller.java
#RestController
public class Controller {
#Autowired
private QService qService;
#PreAuthorize("hasAnyRole('view', 'admin')")
#RequestMapping(value = "/q/{year}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public ResponseEntity<?> listQuotas(#PathVariable Integer year) {
return new ResponseEntity<>(qService.listQs(year), HttpStatus.OK);
}
#RequestMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public ResponseEntity<?> user(HttpServletRequest request) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return new ResponseEntity<>( auth.getPrincipal(), HttpStatus.OK);
}
#PreAuthorize("hasRole('shouldntauthorize')")
#RequestMapping(value = "/unauthorized/{year}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public ResponseEntity<?> unauthorized(#PathVariable Integer year) {
return new ResponseEntity<>(qService.listQs(year), HttpStatus.OK);
}
}
When it works - I am able to hit any of the above methods using HTTP gets and I am getting correct responses. When it's not working, I am constantly getting:
2017-01-05 14:18:47.506 WARN 11252 --- [ (self-tuning)'] o.s.web.servlet.PageNotFound : No mapping found for HTTP request with URI [/user] in DispatcherServlet with name 'dispatcherServlet'
I can verify in the logs that when Spring Boot initializes the application is also sets the correct mapping URL.
Any ideas what could be the problem here?
when you say "intermittently" I tend to think that the problem is with Spring startup configuration.
So, I'd be weary on the fact that you have #ComponentScan twice, and with different packages.
Could you try removing
#ComponentScan(basePackages = { "com.controller", "com.service", "com.dao"})
from class WebConfiguration.java and
#ComponentScan({ "com.subject", "com.custom" })
from class WebSecurityConfiguration.java, and replace them with a single
#ComponentScan(basePackages = { "com.controller", "com.service", "com.dao", "com.subject", "com.custom"})
in the main SpringBoot class?

Logging out (or switching user) from spring security oauth2

I am having a problem with logout with spring security and oauth2. I have a client gateway which houses the UI, an authorization server, and several resource servers. The expected behavior is that when the user clicks on the logout link, they are just logged out from the client gateway and when the page reloads it should redirect back to the authorization server since the entire app is locked down. If the user is logged out of the authorization server that is acceptable too. The UI is in angular. The logout function I am calling is
$scope.logout = function() {
$http.post('/logout', {}).success(function() {
$location.path("/");
}).error(function(data) {
$rootScope.authenticated = false;
});
}
When this is called, the resposne I get is:
XMLHttpRequest cannot load http://localhost:9999/auth/oauth/authorize?client_id=ui&redirect_uri=http://localhost:8080/login?logout&response_type=code&state=mj2HVD. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.
Obviously this is a cross site error which does not make any sense to me because the login portion works fine.
AuthServerApplication
#SpringBootApplication
public class AuthServerApplication extends WebMvcConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(AuthServerApplication.class);
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
protected static class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Autowired // <-- This is crucial otherwise Spring Boot creates its own
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
log.info("Defining inMemoryAuthentication (2 users)");
auth
.inMemoryAuthentication()
.withUser("user").password("password")
.roles("USER")
.and()
.withUser("admin").password("password")
.roles("USER", "ADMIN")
;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.httpBasic().disable()
.anonymous().disable()
.authorizeRequests().anyRequest().authenticated()
;
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Value("${config.oauth2.privateKey}")
private String privateKey;
#Value("${config.oauth2.publicKey}")
private String publicKey;
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public JwtAccessTokenConverter tokenEnhancer() {
log.info("Initializing JWT with public key:\n" + publicKey);
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(tokenEnhancer());
}
/**
* Defines the security constraints on the token endpoints /oauth/token_key and /oauth/check_token
* Client credentials are required to access the endpoints
*
* #param oauthServer
* #throws Exception
*/
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("isAnonymous() || hasRole('ROLE_TRUSTED_CLIENT')") // permitAll()
.checkTokenAccess("hasRole('TRUSTED_CLIENT')"); // isAuthenticated()
}
/**
* Defines the authorization and token endpoints and the token services
*
* #param endpoints
* #throws Exception
*/
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// Which authenticationManager should be used for the password grant
// If not provided, ResourceOwnerPasswordTokenGranter is not configured
.authenticationManager(authenticationManager)
// Use JwtTokenStore and our jwtAccessTokenConverter
.tokenStore(tokenStore())
.accessTokenConverter(tokenEnhancer())
;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// Confidential client where client secret can be kept safe (e.g. server side)
.withClient("confidential").secret("secret")
.authorizedGrantTypes("client_credentials", "authorization_code", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:9000/admin").autoApprove(true)
.and()
.withClient("ui").secret("secret")
.authorizedGrantTypes("client_credentials", "authorization_code", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:8080/").autoApprove(true)
.and()
// Public client where client secret is vulnerable (e.g. mobile apps, browsers)
.withClient("public") // No secret!
.authorizedGrantTypes("client_credentials", "implicit")
.scopes("read")
.redirectUris("http://localhost:9000/").autoApprove(true)
.and()
// Trusted client: similar to confidential client but also allowed to handle user password
.withClient("trusted").secret("secret")
.authorities("ROLE_TRUSTED_CLIENT")
.authorizedGrantTypes("client_credentials", "password", "authorization_code", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:9000/").autoApprove(true)
;
}
}
GatewayApplication
#EnableZuulProxy
#EnableDiscoveryClient
#SpringBootApplication
#EnableCircuitBreaker
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
#Configuration
#EnableOAuth2Sso
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.logout()
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and()
.authorizeRequests()
.antMatchers("/login", "/beans", "/user").permitAll()
.anyRequest().authenticated().and()
.csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterBefore(new RequestContextFilter(), HeaderWriterFilter.class)
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
}
It would also be acceptable if I could just switch the user. If I missed any relevant code, you can look at the repo here.

Spring Boot Rest service with oAuth2 Security credentials from database

Could anyone please help me with an example of a Spring Boot application that contains a Rest Service with endpoints protected by Spring Security using oAuth2 with user credentials from a MySQL database?
How about this one: https://github.com/spring-projects/spring-security-oauth/tree/master/tests/annotation/jdbc (it's not MySQL, but it's JDBC, so the transformation is trivial)?
Please refer to https://github.com/royclarkson/spring-rest-service-oauth/
and perform following changes, It uses primary datasource defined in application.properties,
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "rest_api";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/users").hasRole("ADMIN")
.antMatchers("/review").authenticated()
.antMatchers("/logreview").authenticated()
.antMatchers("/oauth/token").authenticated()
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
;
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
DataSource dataSource;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(new JdbcTokenStore(dataSource))
.authenticationManager(this.authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(dataSource);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setAccessTokenValiditySeconds(300);
tokenServices.setRefreshTokenValiditySeconds(6000);
tokenServices.setTokenStore(new JdbcTokenStore(dataSource));
return tokenServices;
}
}
}