I am trying to send a custom error for anonymous unauthorized requests through a custom AuthenticationEntryPoint
public class AccessDeniedAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Autowired
private Messages messages;
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException {
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON);
response.setCharacterEncoding("UTF-8");
final ObjectMapper mapper = new ObjectMapper();
final ApplicationErrorResponse error = new ApplicationErrorResponse(
Message.PERMISSION_FAILED.toString(),
messages.get(Message.PERMISSION_FAILED.toString()));
if (response.getWriter() != null) {
response.getWriter().write(mapper.writeValueAsString(error));
response.getWriter().flush();
}
}
The Messages bean encapsulates the default MessageSource (messages.properties) and returns the values through a MessageSourceAccessor
#Component
public class Messages {
#Autowired
private MessageSource messageSource;
private MessageSourceAccessor accessor;
#PostConstruct
private void init() {
accessor = new MessageSourceAccessor(messageSource);
}
public String get(String code) {
return accessor.getMessage(code);
}
}
However when an AccessDendiedException is raised and the commence method in the AccessDeniedAuthenticationEntryPoint is called, the injected/autowired Messages object is null.
I just couldn't get Spring to successfully inject Messages in a custom Authentication Entry Point.
I even tried registering a bean of type MessageSource
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
and tried autowiring it in the AuthenticationEntryPoint but it didn't work too.
How can I successfully inject/autowire a MessageSource in a custom AuthenticationEntryPoint?
Related
I implemented a OncePerRequestFilter, where in the doFilterInternal() I would like to use an utilization class, that used JdbcTemplate and user data from a properties file. I realized that it couldn't reach the data from the properties file (database connection and variables) and has null value all the time. As I found on the internet it's, because of the different context.
I could successfully setup a new jdbc datasource locally, but I wouldn't like to duplicate the code, so I would like to inject simply the sources the same way as I did everywhere else like in RestControllers (#Value, #Autowired).
Any idea, how could I inject these in my utilization class that will be used in the servlet filter or directly in my filter?
Thank you!
UPDATE - code snippets:
In the RestController, the injection of JdbcTemplate works properly, but in the filter I cannot inject it, always throws nullPointerException.
#SpringBootApplication
public class AsdApplication {
public static void main(String[] args) {
SpringApplication.run(AsdApplication.class, args);
}
public static class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
DelegatingFilterProxy delegateFilterProxy = new DelegatingFilterProxy();
delegateFilterProxy.setTargetBeanName("MyFilter");
return new Filter[] { delegateFilterProxy };
}
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return null;
}
}
}
#RestController
public class RestCtrl {
#Autowired
private JdbcTemplate jdbcTemplate;
#GetMapping("/test")
public ResponseEntity<String> getTest() {
String result = jdbcTemplate.queryForObject("<query>", String.class);
System.out.println("result in ctrl: " + result);
return new ResponseEntity<>("asd ad asd asd asd", HttpStatus.OK);
}
}
#Component(value = "MyFilter")
public class MyFilter extends OncePerRequestFilter {
#Autowired
private JdbcTemplate jdbcTemplate;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String result = jdbcTemplate.queryForObject("<query>", String.class);
System.out.println("result in filter: " + result);
User currentUser = new User("username", "password", new ArrayList<>());
UsernamePasswordAuthenticationToken authenticatedUser = new UsernamePasswordAuthenticationToken(
currentUser, null, currentUser.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
filterChain.doFilter(request, response);
}
}
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
spring.datasource.url=jdbc:<sqlserver>
spring.datasource.username=<user>
spring.datasource.password=<pass>
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
As you are actually using Spring Boot and want to make it part of the Spring Security filter chain (which is something different!) what you need to do is
Create an #Bean method to create the filter and make it a bean
Create an #Bean method and add a FilterRegistration bean to prevent the bean from being registered as a filter by Spring Boot
Configure Spring Security.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(myFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
#Bean
public MyFilter myFilter() {
return new MyFilter();
}
#Bean
public FilterRegistrationBean<MyFilter> myFilterRegistationBean() {
FilterRegistationBean frb = new FilterRegistrationBean(myFilter());
frb.setEnabled(false);
return frb;
}
Finally remove the #Component from your MyFilter as you don't need it and it would create an additional instance. All prior changes (like the ApplicationInitializer etc. you can remove.
NOTE: As you are using Spring Security and somehow use this for authentication, instead of extending OncePerRequestFilter I suggest you extend the Spring Security AbstractAuthenticationProcessingFilter which integrates better with Spring Security (like fireing events for authentication, logging etc.).
I see you are creating a new instance of MyFilter instead of using the one managed by Spring with #Component(value = "MyFilter")
httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
Hence you will hit a NPE since jdbcTemplate is null. You can inject the instance managed be Spring instead of creating a new one.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("MyFilter")
private MyFilter myFilter;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
}
You should use this:
Through this class you can get different Spring Boot Beans in a non Bean class.
#Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext appContext)
throws BeansException {
ctx = appContext;
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}
Then after creating it, get your bean this way:
ApplicationContext appCtx = ApplicationContextUtils.getApplicationContext();
// Here you get your dependency
ARequiredClass dependency = appCtx.getBean(ARequiredClass.class);
I have a message bundle under my resources in src/main the resources with messages are named messages_en_US.properties i.e. and I am getting the message to my JSP.
#Component
public class Messages {
private MessageSource messageSource;
#Autowired
public Messages(MessageSource messageSource) {
this.messageSource = messageSource;
}
private MessageSourceAccessor accessor;
#PostConstruct
private void init() {
accessor = new MessageSourceAccessor(messageSource);
}
public String get(String code) {
return accessor.getMessage(code);
}
}
When I used the code above without any beans I had an exception that informed me there is no code for a message. So I added a BeanHelper class as below.
#Configuration
#ComponentScan("com")
public class BeansHelper {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
return messageSource;
}
}
When now I am trying to go on localhost:8000/ I am getting an 404 that says there is no message available. What is wrong in this code? My messages are under src/main in the resources. But the JSP of course are in WEB-INF/views/ any ideas guys how to solve the problem? I correctly added message properties file to my Local.
i've a big problem and no idea how to solve it...
I need to use customAuthenticationManager for third party log-in in my spring boot application, but when i declare custom authenticator i get :
Handling error:
ClassCastException, java.lang.String cannot be cast to com.nexus.demooauth.models.User
If i use default authentication manager (the one that comes with spring boot) everything works fine.
Here is Websecurity.java
#Configuration
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return new CustomAuthenticationManager();
}
AuthorizationServerConfig.java
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
UserDetailsService customUserDetailsService;
#Autowired
DataSource dataSource;
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public PasswordEncoder passwordEncoder() {
return new Plainencoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
//configurer.userDetailsService(customUserDetailsService);
configurer.authenticationManager(authenticationManager);
configurer.tokenEnhancer(tokenEnhancer());
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("gigy").secret("secret").accessTokenValiditySeconds(8400)
.scopes("read", "write").authorizedGrantTypes("password", "refresh_token");
}
CustomAuthenticationManager.java
#Service
public class CustomAuthenticationManager implements AuthenticationManager{
private final Logger logger = LoggerFactory.getLogger(CustomAuthenticationManager.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String pw = authentication.getCredentials().toString();
logger.info("was here" + username.toString() + " , " + pw.toString());
return new UsernamePasswordAuthenticationToken(username, pw, authentication.getAuthorities());
}
It actually prints in logger
2018-05-15 17:58:34.453 INFO 7212 --- [nio-8089-exec-1] c.n.d.s.CustomAuthenticationManager : was heretest , test
When debugging it breaks when returning new UsernamePasswordAuthenticationToken in some obfuscated class.
Actually found the anserw.
The problem was in CustomTokenEnhancer that was making this invalid conversion.
I have to create different messageSources programmatically and put them in a Bean in order to use the correct one when needed.
The application must have a messageSource for each of our Customers, so i created a Configuration class
#Configuration
public class MessageSourceConfig implements BeanFactoryAware {
private BeanFactory beanFactory;
#Autowired
private ICompanyService service;
private Map<Company, MessageSource> messageSourceMap = new HashMap<Company, MessageSource>();
// default messageSource
#Bean
#Primary
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setCacheSeconds(5);
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#PostConstruct
public void onPostConstruct() {
ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
Iterable<Company> companies = service.findAll();
for(Company c : companies) {
String beanName= c.getSlug()+"_messageSource";
MessageSource bean = getCompanyMessageSource(c);
configurableBeanFactory.registerSingleton(beanName, bean);
messageSourceMap.put(c, bean);
}
}
private MessageSource getCompanyMessageSource(Company company) {
ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
ms.setBasename("classpath:" + company.getSlug() + "/messages");
ms.setUseCodeAsDefaultMessage(true);
ms.setCacheSeconds(5);
ms.setDefaultEncoding("UTF-8");
return ms;
}
public MessageSource companyMessageSource(Company company) {
return messageSourceMap.get(company);
}
In this way we have a default messageSource and one specific messageSource for each Company.
The idea was to put this specific messageSources into a Map and then accessing the correct one from the map when we need it.
The problem is that companyMessageSource should be a bean, but i cannot pass a parameter to the bean, how can i access dynamically the correct source?
I am not entirely sure I understand how you want to use the created beans, but one way to get the registered singletons of MessageSource is to get them programmatically something like this:
#Service
public class CompanyService {
#Autowired
private ApplicationContext applicationContext;
public void useCompanySpecificMessageSource(Company c) {
MessageSource ms = applicationContext.getBean(c.getSlug() + "_messageSource");
log.debug(ms.getMessage("code", null, new Locale("en", "GB"));
}
}
Hope this helps.
I am trying to write a custom bean validator and show validation messages based on locale on the user interface.
To do that, I created a validator like the following:
#Component
public class MyCustomValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyClass.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyClass myObject = (MyClass)target;
if (StringUtils.isBlank(myObject.getName())) {
errors.rejectValue("name", "{myproject.myclass.validation.name});
}
}
}
I have also registered messageSource and validator beans:
#Configuration
#ComponentScan(basePackages = "com.mycompany.myproject")
public class MyProjectWebMvcConfig {
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.addBasenames("locale/myproject");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(this.messageSource());
return validator;
}
}
In my controller I used initBinder to register my validators:
#RestController
#RequestMapping("/api/mytype")
public class MyController extends MyBaseController {
#Autowired
private MyCustomValidator myCustomValidator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
super.initBinder(binder);
binder.addValidators(this.myCustomValidator);
}
#PostMapping(value = "/")
public ResponseEntity<OperationResult<Void>> save(#Valid #RequestBody MyClass myObject, BindingResult bindingResult, HttpServletRequest request) {
if (!bindingResult.hasErrors()) {
...
}
...
}
}
public class MyBaseController {
#Autowired
protected Validator validator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
}
Still, validation error messages appear like {myproject.myclass.validation.name} on the user interface. It seems like messages are not read from messageSource even if I have set validation message source of LocalValidatorFactoryBean.
On the other hand, if I use #NotNull(message = {myproject.myclass.validation.name}) annotation instead of using a custom validator, the validation error message appears correctly.
Couldn't figure out what I am missing.
Try this
errors.rejectValue("name", "myproject.myclass.validation.name");
Check encoding of messageSource files. It should be utf-8.