We are trying to user #PreAuthorize with token authentication.
When we try to use #PreAuthorize then SpringSecurity popsup with login page before an API gets called. We don't need that page as we have our own authentication process.
To skip that page we added
#SpringBootApplication( exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class }) on our main class.
After this the login page was skipped, but then all our API's when we trigger them gave error that Authentication needs to be there in the context.
For this we did below changes
#Configuration
public class MethodSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().addFilter(new AuthFilter(authenticationManagerBean())).authorizeRequests().anyRequest().permitAll();
}
}
#Component
public class AuthFilter implements Filter {
private AuthenticationManager authenticationManager;
public AuthFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain)
throws IOException, ServletException {
final String authorizationHeader = ((HttpServletRequest) request).getHeader("Authorization");
System.out.println("===========Filter called================");
final Authentication authentication = authenticationManager
.authenticate(SecurityContextHolder.getContext().getAuthentication());
System.out.println("===========Authentication================"+authentication);
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated()) {
// set authentication in security context holder
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterchain.doFilter(request, response);
}
}
Now when I'm getting error that no bean is present for AuthenticationManager.
I tried by many other ways still the bean is not getting injected in the filter
Can you comment on this ?
#Configuration
public class MethodSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().addFilter(new AuthFilter(authenticationManagerBean())).authorizeRequests().anyRequest().permitAll();
}
}
#Component
public class AuthFilter implements Filter {
#Autowired //--->use this
private AuthenticationManager authenticationManager;
public AuthFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain)
throws IOException, ServletException {
final String authorizationHeader = ((HttpServletRequest) request).getHeader("Authorization");
System.out.println("===========Filter called================");
final Authentication authentication = authenticationManager
.authenticate(SecurityContextHolder.getContext().getAuthentication());
System.out.println("===========Authentication================"+authentication);
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated()) {
// set authentication in security context holder
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterchain.doFilter(request, response);
}
}
try something like this it might help:
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
you can also refer this How To Inject AuthenticationManager using Java Configuration in a Custom Filter
Once try like this
#Configuration
public class MethodSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
AuthFilter authFilter;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().addFilter(authFilter).authorizeRequests().anyRequest().permitAll();//change is here
}
}
#Component
public class AuthFilter implements Filter {
private AuthenticationManager authenticationManager;
public AuthFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain)
throws IOException, ServletException {
final String authorizationHeader = ((HttpServletRequest) request).getHeader("Authorization");
System.out.println("===========Filter called================");
final Authentication authentication = authenticationManager
.authenticate(SecurityContextHolder.getContext().getAuthentication());
System.out.println("===========Authentication================"+authentication);
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated()) {
// set authentication in security context holder
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterchain.doFilter(request, response);
}
}
Instead of creating Java object of auth filter use spring bean AuthFilter
Related
[SecurityConfig]
#Slf4j
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig {
private final AuthenticationConfiguration authenticationConfiguration;
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
/*.headers().disable()*/
.anyRequest().permitAll()
.and()
.addFilterBefore(new JwtAuthenticationFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
[JwtAuthenticationFilter]
#RequiredArgsConstructor
#Slf4j
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("Check Run");
ObjectMapper om = new ObjectMapper();
try {
AdvertiserUserDto.Read read = om.readValue(request.getInputStream(), AdvertiserUserDto.Read.class);
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(read.getUserLoginId(), read.getPassword());
log.info(authenticationToken.getPrincipal().toString());
log.info(authenticationToken.getCredentials().toString());
return authenticationManager.authenticate(authenticationToken);
}
catch (IOException e) {
e.printStackTrace();
log.error("IO Exception");
}
return null;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
log.info("Success");
super.successfulAuthentication(request, response, chain, authResult);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
log.info("Fail");
super.unsuccessfulAuthentication(request, response, failed);
}
}
[SecurityUserDetailsService]
#Slf4j
#RequiredArgsConstructor
#Service
public class SecurityUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
#Transactional
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("loadUserByUsername"); // **not Run**
UserEntity user = userRepository.findByUserLoginId(username).orElseThrow(() -> new UsernameNotFoundException("no Search User"));
return new SecurityUserDetails(
user,
/*Collections.singleton(new SimpleGrantedAuthority("ROLE_" + user.getType().getRoll()))*/
Collections.singleton(new SimpleGrantedAuthority("ROLE_" + "USER"))
);
}
}
[SecurityUserDetails]
#RequiredArgsConstructor
#Data
public class SecurityUserDetails implements UserDetails {
private final UserEntity user;
private final Collection<? extends GrantedAuthority> authorities;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUserId().toString();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
Please note that I am struggling at a very basic level.
Here's the process I've tried so far:
Login attempt (user, pw)
Security Chain is executed
A filter that inherits from UsernamePasswordAuthenticationFilter is executed
It has been confirmed that ID and PW are normally passed as parameters.
Execute the authenticate() method of AuthenticationManager to check if ID and PW are correct.
The loadUserByUsername() method, which inherited UserDeatilsService, is not executed, and immediately the unsuccessfulAuthentication() method is executed and fails.
I attach the code.
According to my plan, validation should be done via loadUserByUsername().
But why does the validation job fail immediately without even being executed?
Result :
c.b.k.c.c.t.JwtAuthenticationFilter : Check Run
c.b.k.c.c.t.JwtAuthenticationFilter : test
c.b.k.c.c.t.JwtAuthenticationFilter : 1234
c.b.k.c.c.t.JwtAuthenticationFilter : Fail
I answer myself how I found it.
#Slf4j
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig {
private final SecurityUserDetailsService securityUserDetailsService;
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(securityUserDetailsService);
AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
httpSecurity
.cors()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.authenticationManager(authenticationManager)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
/*.headers().disable()*/
.anyRequest().permitAll()
.and()
.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Here's how I discovered and succeeded by other advice.
I think there are some things wrong with this method or something that needs to be improved.
I don't know if it's the right way, but I'm writing it down for others and for improvement.
AuthenticationManagerBuilder authenticationManagerBuilder = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(securityUserDetailsService);
AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
The key is here.
I approached the problem in that the userDetatilsService that I have customized and extended cannot be informed (cannot be injected) to the AuthenticationManager.
I'm trying to get an endpoint not accessible (503 error?) without Authorization: Bearer token header
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate")
.permitAll()
.antMatchers("/api/admin/**")
.fullyAuthenticated()
.anyRequest().authenticated().and().
exceptionHandling()
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests();
httpSecurity.addFilterBefore(jwtRequestFilter,
UsernamePasswordAuthenticationFilter.class);
}
#RestController
#CrossOrigin
#RequestMapping("/api/admin")
public class AdminController {
#RequestMapping("/test")
public String testAdmin() {
return "OK; secret test admin";
}
}
however I can access it just fine
What should I change in my configure method?
EDIT:
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private JwtUtil jwtUtil;
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder
.getContext().getAuthentication() == null) {
UserDetails userDetails = this
.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken
usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
It seems that the jwtRequestFilter's doFilterInternal method never runs: I tried setting the breakpoints in the debugger and the execution never stopped there.
EDIT: whole SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
public SecurityConfig(
UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Bean
DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider =
new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(this.userDetailsService);
return daoAuthenticationProvider;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/api/login").permitAll()
// all other requests need to be authenticated
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter,
UsernamePasswordAuthenticationFilter.class);
}
#Bean
BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
HTTP response 503 means service unavailable. You should get 401 Unauthorized when token is missing.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/login").permitAll()
// all other requests need to be authenticated
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Using AuthenticationEntryPoint.
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -1L;
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
I managed to solve it. Turns out the problem was in me not having correct configurations, so the SecurityConfig never even got applied. I fixed it this way:
WebConfig.java:
#Configuration
#ComponentScan("testproject")
#EnableWebMvc
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "testproject",
entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager")
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver bean = new InternalResourceViewResolver();
bean.setViewClass(JstlView.class);
bean.setPrefix("/WEB-INF/view/");
bean.setSuffix(".html");
return bean;
}
#Bean
public UserDetailsService userDetailsService() {
UserDetailsService userDetailsService =
new UserDetailsServiceImpl();
return userDetailsService;
}
}
MyAppInitializer.java (notice the commented out sc.addListener(new ContextLoaderListener(root)); line, it must be like that, otherwise there are errors - the fix was suggested to me in another SO question):
public class MyAppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(final ServletContext sc) throws ServletException {
System.out.println("onStartup!");
AnnotationConfigWebApplicationContext root =
new AnnotationConfigWebApplicationContext();
root.register(WebConfig.class);
root.setServletContext(sc);
root.scan("testproject");
//sc.addListener(new ContextLoaderListener(root));
ServletRegistration.Dynamic appServlet =
sc.addServlet("dispatcher", new DispatcherServlet(new GenericWebApplicationContext()));
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/");
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {SecurityConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
SecurityWebApplicationInitializer.java:
public class SecurityWebApplicationInitializer extends
AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(SecurityConfig.class, WebConfig.class);
}
}
I'm trying pass filter JWTLoginFilter to WebSecurityConfig WebSecurityConfigurerAdapter by #Autowired annotation. The problem arise when JWTLoginFilter try get AuthenticationManager from WebSecurityConfig.
When I start server, I get this error:
Description:
The dependencies of some of the beans in the application context form
a cycle:
JWTLoginFilter defined in file
[C:\Users\user\workspace\backend\target\classes\pl\dn\schoolsystem\service\jwt\JWTLoginFilter.class]
webSecurityConfig (field
pl.dn.schoolsystem.service.jwt.JWTLoginFilter
pl.dn.schoolsystem.config.WebSecurityConfig.jwtLoginFilter)
error image
I think this circular dependency injection. I got stuck on this and i have no idea how to solve it.
WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
private UserSecurityService userSecurityService;
#Autowired
JWTLoginFilter jwtLoginFilter;
private static final String Salt = "salt"; // should be protected better
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12, new SecureRandom(Salt.getBytes()));
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
authorizeRequests().antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
//.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
//UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtLoginFilter,
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
}
}
JWTLoginFilter:
#Component
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter{
#Autowired
UserService userService;
#Autowired
public JWTLoginFilter(#Value("/login") String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException,
IOException, ServletException {
System.out.println("Jestem w JwtLogginFilter.attemptAuthentication -------------------------------------");
AccountCredentials creds = new ObjectMapper()
.readValue(req.getInputStream(), AccountCredentials.class);
User user = userService.findByUsername(creds.getUsername());
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
user.getAuthorities()
)
);
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
System.out.println("Jestem w JWTLogginFilter.successfulAuthentication -------------------------------------- ");
System.out.println("authResult.getName(): " + authResult.getName());
TokenAuthenticationService.addAuthentication(response, authResult.getName());
}
}
I'm using Spring Boot 1.5.4. Thanks for suggestions
Your WebSecurityConfig explicitly requests JWTLoginFilter to be injected in it, and JWTLoginFilter requests AuthenticationManager to be injected in its constructor. AuthenticationManager is supplied by WebSecurityConfig, so you have a circular dependency.
Remove #Component annotation from JWTLoginFilter and define the filter as a bean in WebSecurityConfig:
#Bean
public JWTLoginFilter jwtLoginFilter() {
return new JWTLoginFilter("/login", authenticationManager());
}
You will probably also need to inject UserService manually in this method (for example, via constructor).
Thanks for help Roman Puchkovskiy I can correct my code. Final result of my code:
WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
private UserSecurityService userSecurityService;
private static final String Salt = "salt"; // should be protected better
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12, new SecureRandom(Salt.getBytes()));
}
#Bean
public JWTLoginFilter jwtLoginFilter() throws Exception {
return new JWTLoginFilter("/login", authenticationManager());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
authorizeRequests().antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
// .addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
// UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtLoginFilter(),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
}
}
JWTLoginFilter:
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter{
#Autowired
UserService userService;
public JWTLoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException,
IOException, ServletException {
System.out.println("Jestem w JwtLogginFilter.attemptAuthentication -------------------------------------");
AccountCredentials creds = new ObjectMapper()
.readValue(req.getInputStream(), AccountCredentials.class);
User user = userService.findByUsername(creds.getUsername());
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
user.getAuthorities()
)
);
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
System.out.println("Jestem w JWTLogginFilter.successfulAuthentication -------------------------------------- ");
System.out.println("authResult.getName(): " + authResult.getName());
TokenAuthenticationService.addAuthentication(response, authResult);
}
}
I want to replace the default access denied page:
With my custom page and my approach was this:
#Configuration
#EnableWebSecurity
public class SecurityContextConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().maximumSessions(1)
.sessionRegistry(sessionRegistry()).expiredUrl("/");
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/security/checkpoint/for/admin/**").hasRole("ADMIN")
.antMatchers("/rest/users/**").hasRole("ADMIN").anyRequest()
.authenticated().and().formLogin().loginPage("/")
.defaultSuccessUrl("/welcome").permitAll().and().logout()
.logoutUrl("/logout");
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public AuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
return daoAuthenticationProvider;
}
#Bean
public ProviderManager providerManager() {
List<AuthenticationProvider> arg0 = new CopyOnWriteArrayList<AuthenticationProvider>();
arg0.add(daoAuthenticationProvider());
return new ProviderManager(arg0);
}
#Bean(name = "myAuthenticationManagerBean")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return providerManager();
}
#Bean
public ExceptionTranslationFilter exceptionTranslationFilter() {
ExceptionTranslationFilter exceptionTranslationFilter =
new ExceptionTranslationFilter(new CustomAuthenticationEntryPoint());
exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler());
return exceptionTranslationFilter;
}
#Bean
public AccessDeniedHandlerImpl accessDeniedHandler() {
AccessDeniedHandlerImpl accessDeniedHandlerImpl = new
AccessDeniedHandlerImpl();
accessDeniedHandlerImpl.setErrorPage("/page_403.jsp");
System.out.println("ACCESS DENIED IS CALLED......");
return accessDeniedHandlerImpl;
}
private class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint{
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authenticationException) throws IOException,
ServletException {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access denied.");
}
}
}
But with this config above I'm still not getting the job done and seeing the same
Are there more bean which must be injected for this purpose?
Disclaimer : this is not only solution, but a working one.
In this case my approach would be as simple as possible which is add this method in your SecurityContext
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().maximumSessions(1)
.sessionRegistry(sessionRegistry()).expiredUrl("/");
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/security/checkpoint/for/admin/**").hasRole("ADMIN")
.antMatchers("/rest/users/**").hasRole("ADMIN").anyRequest()
.authenticated().and().formLogin().loginPage("/")
.defaultSuccessUrl("/welcome").permitAll().and().logout()
.logoutUrl("/logout").and()
.exceptionHandling().accessDeniedPage("/page_403");//this is what you have to do here to get job done.
}
Reference: Custom 403 Page in Spring Security.
As #M. Deinum pointed out, you should tell Spring Security how to incorporate these beans. Anyway, there is a much simpler way for what you're trying to achieve:
#Configuration
#EnableWebSecurity
public class SecurityContextConfigurer extends WebSecurityConfigurerAdapter {
// Rest omitted
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// The usual stuff
.exceptionHandling()
.accessDeniedPage("/page_403.jsp")
.authenticationEntryPoint((request, response, authException) -> {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
});
}
}
It looks very strange behavior, probably a bug.
I have a Spring boot 1.3.2 as backend API using Rest Service and I have another application using Angular 2 consuming these services.
All the security stuff is working ok with JWT Token, I can restricted my services for logged users, I can check the user logged and so on. Authorization is not working 100%, If I add on my services #Secured or #PreAuthorize with some user role this work with swagger and with my MockMvc tests using #WithMockUser(roles="ROLE_TEST") so it's configured OK.
The problem is that Authorization with #Secured or #PreAuthorize is not working when I'm accessing via angular application, All my requests that has #Secured or #PreAuthorize I receive Status 403.
authentication.getAuthorities() that all my roles is being loaded perfectly
Controller:
#RequestMapping(method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
#Secured("ROLE_MANTER_INSTITUICAO")
public List<HierarquiaInstituicao> getAll() {
return service.findAll();
}
Security Config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled=true, jsr250Enabled=true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private UserService userService;
private final TokenAuthenticationService tokenAuthenticationService;
public SpringSecurityConfig() {
super(true);
this.userService = new UserService();
tokenAuthenticationService = new TokenAuthenticationService(userService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().and().anonymous().and().servletApi().and()
.authorizeRequests()
// Allow anonymous logins
.antMatchers("/api/auth/**").permitAll()
// All other request need to be authenticated
.anyRequest().authenticated().and()
// Custom Token based authentication based on the header
// previously given to the client
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserService userDetailsService() {
return userService;
}
#Bean
public TokenAuthenticationService tokenAuthenticationService() {
return tokenAuthenticationService;
}
}
My filter:
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService authenticationService;
public StatelessAuthenticationFilter(TokenAuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
Authentication authentication = authenticationService.getAuthentication(httpRequest);
if(authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
else {
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
}
SecurityContextHolder.getContext().setAuthentication(null);
}
}