Custom HTTP 403 page not working in Spring Security - java

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);
});
}
}

Related

Unable to create AuthenticationManager bean

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

Spring Security: make endpoint controller only accessible with Authorization: Bearer token header

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);
}
}

Spring Oauth2 redirect uri doesn't change

I'm working on OAuth2 Authorization in Spring and try to implement authorization code grant flow. Now I have two applications. Client side and authorization server side. When I open secured /client/hello it redirect me to oauth2 login page, after that a get /oauth/authorize link, but in redirect_uri value always is login page on client side and it even doesn't change manually in browser. How I can change it? If i change redirect uri to /client/login in auth server config it redirects and gives me authorization code, but invokes unauthorized error.
Client
Controller:
#RestController
public class Controller {
#GetMapping("/hello")
public String hello() {
return "Hello world!!";
}
#GetMapping("/public")
public String publicPage() {
return "This is public!!";
}
#GetMapping("/callback")
public String login(#RequestParam("code") String code) {
return code;
}
}
Client security config:
#Configuration
#EnableOAuth2Sso
public class ClientSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/error**", "/public**").permitAll()
.anyRequest().authenticated();
}
}
Client properties:
security.oauth2.client.client-id=007314
security.oauth2.client.client-secret=MDA3MzE0
security.oauth2.client.grant-type=password
security.oauth2.client.scope=read
security.oauth2.client.pre-established-redirect-uri=http://localhost:8081/client/public
security.oauth2.client.access-token-uri=http://localhost:8082/auth/oauth/token
security.oauth2.client.user-authorization-uri=http://localhost:8082/auth/oauth/authorize
security.oauth2.client.authentication-scheme=form
security.oauth2.resource.user-info-uri=http://localhost:8081/client/hello
security.oauth2.resource.id=resource-server-rest-api
server.port=8081
server.servlet.context-path=/client
Authorization Server
Server config:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
#Qualifier("authenticationManagerBean")
private final AuthenticationManager authenticationManager;
#Autowired
public AuthorizationServer(PasswordEncoder passwordEncoder, AuthenticationManager authenticationManager) {
this.passwordEncoder = passwordEncoder;
this.authenticationManager = authenticationManager;
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(passwordEncoder)
.allowFormAuthenticationForClients();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("007314")
.secret(passwordEncoder.encode("007314"))
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("read")
.resourceIds("resource-server-rest-api")
.autoApprove(true)
.redirectUris("http://localhost:8081/client/hello");
}
#Bean
public TokenStore tokenStore(){
return new JwtTokenStore(defaultAccessTokenConverter());
}
#Bean
public JwtAccessTokenConverter defaultAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(defaultAccessTokenConverter())
.authenticationManager(authenticationManager);
}
}
Server security config:
#EnableWebSecurity
#Order(1)
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
.withUser("qwerty")
.password(passwordEncoder().encode("12345"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/error**", "/login**", "/oauth/authorize**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
}
Resource Server
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource-server-rest-api";
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/client/hello").access("#oauth2.hasScope('read')");
}
}
Server properties:
server.port=8082
server.servlet.context-path=/auth
Add also: security.oauth2.client.useCurrentUri=false into client.properties.

Cannot pass AuthenticationManager to custom filter by #Autowired

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);
}
}

How to configure a custom filter programatically in Spring Security?

On user authentication i need to retrieve his remote address and remote host.
I'm trying to implement a custom filter to support this, but i'm getting "authenticationManager must be specified".
Another doubt is... What is the correct way to register a custom filter using programmatically ?
Configuration using annotations:
#Configuration
#EnableWebSecurity
public class SecurityApplicationConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private SCAAuthenticationFilter scaAuthenticationFilter;
#Autowired
private SCAAuthenticationProvider scaAuthenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(scaAuthenticationProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(scaAuthenticationFilter) // What is the right way ?
.addFilterBefore(scaAuthenticationFilter, AbstractAuthenticationProcessingFilter.class) // What is the right way ?
.csrf().disable()
.authorizeRequests()
.antMatchers("/manual/**").authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.failureUrl("/login?error=true")
.defaultSuccessUrl("/manual")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()
.and();
}
}
The custom filter:
#Component
public class SCAAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
String remoteHost = request.getRemoteHost();
String remoteAddr = request.getRemoteAddr();
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
SCAAuthenticationToken scaAuthenticationToken = new SCAAuthenticationToken(username, password, remoteHost, remoteAddr);
setDetails(request, scaAuthenticationToken);
return getAuthenticationManager().authenticate(scaAuthenticationToken);
}
}
You need set a authenticationManagerBean for your extended filter and config it corr
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public ExUsernamePasswordAuthenticationFilter exUsernamePasswordAuthenticationFilter()
throws Exception {
ExUsernamePasswordAuthenticationFilter exUsernamePasswordAuthenticationFilter = new ExUsernamePasswordAuthenticationFilter();
exUsernamePasswordAuthenticationFilter
.setAuthenticationManager(authenticationManagerBean());
return exUsernamePasswordAuthenticationFilter;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
RequestMatcher requestMatcher = new RequestMatcher() {
#Override
public boolean matches(HttpServletRequest httpServletRequest) {
if (httpServletRequest.getRequestURI().indexOf("/api", 0) >= 0) {
return true;
}
return false;
}
};
http
.addFilterBefore(exUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
...
}
}
Your custom filter extends Spring Security's UsernamePasswordAuthenticationFilter, which means it needs a reference to the authentication manager. I would create your filter as an #Bean in the security configuration, then follow this answer which explains different options for getting a reference to the AuthenticationManager.
In the class that is extending WebSecurityConfigurerAdapter, override the authenticationManagerBean() method and annotate it with #Bean as such:
#Configuration
#EnableWebMvcSecurity
public class YourCustomConfig extends WebSecurityConfigurerAdapter{
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Now you will be able to #Autowire the AuthenticationManager in other classes.
Another option is to create a configurer for your filter and delegate all the work concerning filter initialization to it (the same way UsernamePasswordAuthenticationFilter is configured through the FormLoginConfigurer and AbstractAuthenticationProcessingFilter is configured by the AbstractAuthenticationFilterConfigurer).
public class SCAAuthenticationConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>() {
public static SCAAuthenticationConfigurer scaAuthentication() {
return new SCAAuthenticationConfigurer()
}
#Override
public void configure(HttpSecurity http) throws Exception {
SCAAuthenticationFilter filter = new SCAAuthenticationFilter()
filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
// add postProcess(filter) call if require to autowire some fields
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
}
}
Having such configurer your SecurityConfig will be looking more tidy:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.apply(scaAuthentication())
.and()
// do the rest of configuration
}
}
You may even delegate filter initialization to the ApplicationContext (for example, if you have configuration to inject):
public class FilterWithSettingsConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>() {
#Configuration
#EnableConfigurationProperties(SomeSettings.class)
private static class Config {}
#Override
public void configure(HttpSecurity http) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()
context.parent = http.getSharedObject(ApplicationContext.class)
context.register(Config.class)
context.refresh()
FilterWithSettings filter =
context.getAutowireCapableBeanFactory().createBean(FilterWithSettings.class)
filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class)
}
}
For the comprehensive example take a look at the https://github.com/shiver-me-timbers/smt-spring-security-parent/blob/master/smt-spring-security-jwt/src/main/java/shiver/me/timbers/spring/security/JwtSpringSecurityAdaptor.java

Categories

Resources