I'm really new to Jhipster. I played with Symfony before and did a course on spring boot, so rest application are still kind of new to me.
I was tasked to add authentication to an app previously copied from an another jhipster app.
I already had several bug due to this copy, so i know it was a bad idea to begin with.
The application is just displaying data, the data object is a "site".
Here some of the endpoints a use :
/login
/sites
/api/sites
When authenticated, the "sites" endpoint display a html page and make a request to my backend for a list of sites. The sites display correctly.
When not authenticated, the "sites" endpoint wrongly display a html page and make a request to my backend for a list of sites. The request is answered by a error 500 because the endpoint /api/sites is unattainable, which is what I want.
I don't want the "sites" page to be displayed, I want this page to redirect me to the login page for authentication.
Here is my SecurityConfiguration.java
:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JHipsterProperties jHipsterProperties;
private final RememberMeServices rememberMeServices;
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(
RememberMeServices rememberMeServices,
CorsFilter corsFilter,
JHipsterProperties jHipsterProperties,
SecurityProblemSupport problemSupport
) {
this.rememberMeServices = rememberMeServices;
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
this.jHipsterProperties = jHipsterProperties;
}
@Bean
public AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() {
return new AjaxAuthenticationSuccessHandler();
}
@Bean
public AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler() {
return new AjaxAuthenticationFailureHandler();
}
@Bean
public AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler() {
return new AjaxLogoutSuccessHandler();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web
.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/test/**")
.antMatchers("/management/**");
}
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(corsFilter, CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
.rememberMeParameter("remember-me")
.key(jHipsterProperties.getSecurity().getRememberMe().getKey())
.and()
.authenticationProvider(getProvider())
.formLogin()
.loginProcessingUrl("/api/authentication")
.successHandler(ajaxAuthenticationSuccessHandler())
.failureHandler(getCustomAjaxAuthenticationFailureHandler())
.permitAll()
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler())
.permitAll()
.and()
.headers()
.contentSecurityPolicy(jHipsterProperties.getSecurity().getContentSecurityPolicy())
.and()
.referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
.and()
.featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'self'; payment 'none'")
.and()
.frameOptions()
.deny()
.and()
.authorizeRequests()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/account/reset-password/init").permitAll()
.antMatchers("/api/account/reset-password/finish").permitAll()
.antMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN)
//Doens't do anything ? .antMatchers("/app/sites").hasAuthority(AuthoritiesConstants.USER)
//Doens't do anything ? .antMatchers("/sites").hasAuthority(AuthoritiesConstants.USER)
.antMatchers("/api/**").authenticated()
.antMatchers("/management/**").permitAll();
}
@Bean
public AuthenticationProvider getProvider() {
return new AdesiLdapAuthentication();
}
@Bean
public CustomAjaxAuthenticationFailureHandler getCustomAjaxAuthenticationFailureHandler() {
return new CustomAjaxAuthenticationFailureHandler();
}
}
The first thing I tried was adding the authority to the different restController, but I can't find the one for the pages :
Here is the api/sites RestController
:
@GetMapping("/sites")
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.USER + "\")")
public ResponseEntity<List<SiteDTO>> getAllSites(SiteCriteria criteria, Pageable pageable){
log.debug("REST request to get Traitements by criteria: {}");
// gestion de la pagination
pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(),
Sort.by(Sort.Direction.DESC, SORTING_BY_ID));
List<SiteDTO> page = siteQueryService.findSitesByCriteria(criteria, pageable);
return ResponseEntity.ok().body(page);
}
I tried adding Authority rules on the sites endpoint, doesn't seem to do any changes.
I also have access an another application (named B for clarity), based on the same stack, which is perfectly functional.
Of coursed I tried to compare the sources, the SecurityConfiguration
files have no differences.
Can't find any rules on security for the correctly secured endpoint in the B Application.
Ultimately I would like to know how the B application did this security, but I'm kind of desperate and any way to secure will suffice.
Thanks in advance !
So i succeed at resolving my problem.
My research was flawed, as i tried to add security to the html pages in the back-end, except the backend doesn't manage anything else than api endpoint and management.
The angular server was delivering the pages, so the security has to be configured in the pages components.
In the module.ts file, i have :
const ENTITY_STATES = [...SiteRoute];
It reference the object described here :
export const SiteRoute: Routes = [
{
path: '',
component: SiteComponent,
canActivate: [UserRouteAccessService], //what i added, this work well
data: {
authorities: [], // 'ROLE_USER'
//authorities: ['ROLE_USER'], //this line doesn't seem to do anything ?
// defaultSort: 'id,asc',
pageTitle: 'loginpageApp.site.home.title'
}
},
];
I don't really understand how to use the authorities: [] line, it doesn't work for me but i supposed i forgot a step.
Then, the object UserRouteAccessService control my authentication :
export class UserRouteAccessService implements CanActivate {
constructor(private router: Router, private accountService: AccountService, private stateStorageService: StateStorageService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.accountService.identity().pipe(
map(account => {
if (account) {
const authorities = route.data['authorities'];
if (!authorities || authorities.length === 0 || this.accountService.hasAnyAuthority(authorities)) {
return true;
}
if (isDevMode()) {
console.error('User has not any of required authorities: ', authorities);
}
this.router.navigate(['accessdenied']);
return false;
}
this.stateStorageService.storeUrl(state.url);
this.router.navigate(['/login']);
return false;
})
);
}
}