Using Jersey 1.14 and Spring 3.1.2
I want to create a filter like this: https://gist.github.com/3031495
but in that filter I want access to a provider I created.
I'm getting an IllegalStateException. I suspect something in my lifecycle is hosed up. I can access #Context private HttpServletRequest and pull the session info I need from there, but then two classes have to know about where/how to get my "AuthUser" object.
Any help is appreciated!
My Provider:
#Component
#Provider
public class AuthUserProvider extends AbstractHttpContextInjectable<AuthUser> implements
InjectableProvider<Context, Type> {
private static final Logger LOG = LoggerFactory.getLogger(AuthUserProvider.class);
#Context
HttpServletRequest req;
public void init() {
LOG.debug("created");
}
#Override
// this may return a null AuthUser, which is what we want....remember, a
// null AuthUser means the user hasn't authenticated yet
public AuthUser getValue(HttpContext ctx) {
return (AuthUser) req.getSession().getAttribute(AuthUser.KEY);
}
// InjectableProvider implementation:
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
public Injectable<AuthUser> getInjectable(ComponentContext ic, Context ctx, Type c) {
if (AuthUser.class.equals(c)) {
return this;
}
return null;
}
}
My Filter:
#Component
public class TodoFilter implements ResourceFilter {
private static final Logger LOG = LoggerFactory.getLogger(TodoFilter.class);
#Autowired
private JdbcTemplate todoTemplate;
// this works
#Context
private HttpServletRequest servletRequest;
// this throws a java.lang.IllegalStateException
// #Context
// private AuthUser authUser;
public void init() throws Exception {
LOG.debug("created");
LOG.debug(todoTemplate.getDataSource().getConnection().getMetaData()
.getDatabaseProductName());
}
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest request) {
LOG.debug("checking if {} is authorized to use {}", "my authenticated user",
request.getPath());
// String name = request.getUserPrincipal().getName();
// String[] admins = settings.getAdminUsers();
// for (String adminName : admins) {
// if (adminName.equals(name))
// return request;
// }
// if (authUser.getUsername().equals("jberk")) {
// return request;
// }
// return HTTP 403 if name is not found in admin users
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("You are not authorized!").build());
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return new ContainerResponseFilter() {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
// do nothing
return response;
}
};
}
}
My Service (aka Resource):
#Component
#Path("/rs/todo")
#Produces(MediaType.APPLICATION_JSON)
#ResourceFilters(TodoFilter.class)
public class TodoService {
#GET / #POST methods
}
so I think I figured this out....
I added this to my ResourceFilter:
#Context
private HttpContext ctx;
#Autowired
private AuthUserProvider provider;
then I can do this in the filter method:
public ContainerRequest filter(ContainerRequest request) {
AuthUser authUser = provider.getValue(ctx);
// use authuser in some way
}
this might not be "correct"...but it's working and I don't have code duplication
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
It should be
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
Related
everyone!
I making a defense against password brute force.
I successfully handle AuthenticationFailureBadCredentialsEvent when the user writes the right login and wrong password. But the problem is that I want to return JSON with two fields
{
message : '...' <- custom message
code : 'login_failed'
}
The problem is that it returns standart forbidden exception, but I need custom json.
#Log4j2
#Component
#RequiredArgsConstructor
public class AuthenticationAttemptsHandler {
protected final MessageSource messageSource;
private final AuthenticationAttemptsStore attemptsStore;
private final UserDetailsService userDetailsService;
private final UserDetailsLockService userDetailsLockService;
#EventListener
public void handleFailure(AuthenticationFailureBadCredentialsEvent event) {
val authentication = event.getAuthentication();
val userDetails = findUserDetails(authentication.getName());
userDetails.ifPresent(this::failAttempt);
}
private Optional<UserDetails> findUserDetails(String username) {
...
}
private void failAttempt(UserDetails details) {
val username = details.getUsername();
val attempt = attempt(loginAttemptsProperties.getResetFailuresInterval());
int failures = attemptsStore.incrementFailures(username, attempt);
if (failures >= 2) {
Instant lockedUntil = Instant.now().plus(loginAttemptsProperties.getLockDuration());
userDetailsLockService.lockUser(username, lockedUntil);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
String date = formatter.format(lockedUntil);
String message = String.format("Account will locked till %s", date);
throw new SecurityException(message);
//FailAttemptsExceptionResponse response = new FailAttemptsExceptionResponse(message, //
//"login_ failed"); <---- tryed return entity from this method. Does not work.
// return new ResponseEntity<>(response,HttpStatus.FORBIDDEN);
} else {
String message = String.format("You have %s attempts.", (3 - failures));
// FailAttemptsExceptionResponse response = new FailAttemptsExceptionResponse(message,
"login_ failed");
throw new SecurityException(message);
// return new ResponseEntity<>(response,HttpStatus.FORBIDDEN);
}
}
}
RuntimeException returns 500 status? but I need forbidden
public class SecurityException extends RuntimeException {
private static final long serialVersionUID = 1L;
public SecurityException(String msg) {
super(msg);
}
}
Responce model
public class FailAttemptsExceptionResponse {
String message;
String code;
public FailAttemptsExceptionResponse(String message, String code) {
super();
this.message = message;
this.code = code;
}
public String getMessage() {
return message;
}
public String getCode() {
return code;
}
}
Tried to handle SecurityException and then returns model? but it does not work
#ControllerAdvice
public class SeurityAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(SecurityException.class)
public ResponseEntity<FailAttemptsExceptionResponse> handleNotFoundException(SecurityException ex) {
FailAttemptsExceptionResponse exceptionResponse = new FailAttemptsExceptionResponse(ex.getMessage(),
"login_ failed");
return new ResponseEntity<FailAttemptsExceptionResponse>(exceptionResponse,
HttpStatus.NOT_ACCEPTABLE);
}
}
I successfully handle AuthenticationFailureBadCredentialsEvent, but how can I return JSON response model from the handler with a custom message?
#ControllerAdvice
public class SeurityAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(SecurityException.class)
public ResponseEntity<FailAttemptsExceptionResponse> handleNotFoundException(SecurityException ex, HttpServletResponse response) {
FailAttemptsExceptionResponse exceptionResponse = new FailAttemptsExceptionResponse(ex.getMessage(),
"login_ failed");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return new ResponseEntity<FailAttemptsExceptionResponse>(exceptionResponse,
HttpStatus.NOT_ACCEPTABLE);
}
}
maybe you need to add HttpServletResponse and set the http status.
Register the entry point
As mentioned, I do it with Java Config. I just show the relevant configuration here, there should be other configuration such as session stateless, etc.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(new CustomEntryPoint());
}
}
U can create AuthenticationEntryPoint.
Короч тут почитай xD
Handle spring security authentication exceptions with #ExceptionHandler
Can't figure out how to get my stylesheet to display for the homepage when I added WebApplicationConfig Class and AuthenticationFilter Class. My style she is located at resources/static/css. My current error says net::ERR_TOO_MANY_REDIRECTS...
I have tried adding the following to my WebApplicationConfig, but it didn't work either.
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**")
.addResourceLocations("/static/css/");
}
This is my file structure. I am needing home.css in resources/static/css to be displayed on the home page.
This is my WebApplicationConfig that implements WebMvcConigurer.
#Configuration
public class WebApplicationConfig implements WebMvcConfigurer {
// Create spring-managed object to allow the app to access our filter
#Bean
public AuthenticationFilter authenticationFilter() {
return new AuthenticationFilter();
}
// Register the filter with the Spring container
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationFilter());
}
}
This is my AuthenticationFilter Class that extends HandlerInterceptorAdapter.
public class AuthenticationFilter extends HandlerInterceptorAdapter {
#Autowired
UserRepository userRepository;
#Autowired
AuthenticationController authenticationController;
private static final List<String> whitelist = Arrays.asList("/login", "/register", "/logout", "/", "/css");
private static boolean isWhitelisted(String path) {
for (String pathRoot : whitelist) {
if (path.equals(pathRoot)) {
return true;
}
}
return false;
}
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws IOException {
if (isWhitelisted(request.getRequestURI())) {
// returning true indicates that the request may proceed
return true;
}
HttpSession session = request.getSession();
User user = authenticationController.getUserFromSession(session);
// The user is logged in
if (user != null) {
return true;
}
// The user is NOT logged in
response.sendRedirect("");
return false;
}
}
I have a spring boot application i have many endpoint in this application. When i am hitting endpoint simultaneously JSON response from two different endpoint got interchanged.
For example:
i am hitting /currency/list endpoint and /fee endpoint and i am getting /fee endpoint data in currency/list endpoint and vice versa.
I have no idea why this happening. If anybody can suggest why happening will be helpful.
Also I am using spring security token based auth in this project
CurrencyController.java
#RestController
#RequestMapping(value = UrlConstant.BASE_ADMIN_URI_V1)
#Api(value = "Admin Controller")
#Scope("request")
public class CurrencyController {
public static final Logger logger = LoggerFactory.getLogger(CurrencyController.class);
#Autowired
private LocaleService localService;
#RequestMapping(value = UrlConstant.CURRENCY_LIST_FOR_MARKET, method = RequestMethod.GET)
public ResponseEntity<Object> getCurrencyListForMarket() {
List<Currency> currencyList = currencyService.getCurrencyListForMarket();
ObjectMapper mapper = new ObjectMapper();
try {
String stringList = mapper.writeValueAsString(currencyList);
logger.debug("all currency list as String: {}", stringList);
} catch (JsonProcessingException e) {
logger.debug("error in currency list: {}", e.getMessage());
e.printStackTrace();
}
return ResponseHandler.response(HttpStatus.OK, false, localService.getMessage("currency.list.success"),
currencyList);
}
}
AdminController.java
#RestController
#RequestMapping(value = UrlConstant.BASE_ADMIN_URI_V1)
#Api(value = "Admin Controller")
#Scope("request")
public class AdminController {
#Autowired
private LocaleService localeService;
#Autowired
private FeeService feeService;
#RequestMapping(value = UrlConstant.TRADING_FEES, method = RequestMethod.GET)
public ResponseEntity<Object> getTradingFees() {
TradingFee fee = tradingFeeService.getTradingFee();
return ResponseHandler.response(HttpStatus.OK, true,
localeService.getMessage("admin.transaction.fees.found.success"), fee);
}
}
TokenAuthenticationFilter.java
public class TokenAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
// extract token from header
String token = httpRequest.getHeader("Authorization");
if (token != null && !token.isEmpty()) {
AuthenticationTokenRepo authenticationTokenRepository = WebApplicationContextUtils
.getRequiredWebApplicationContext(httpRequest.getServletContext())
.getBean(AuthenticationTokenRepo.class);
// check whether token is valid
AuthenticationToken authToken = authenticationTokenRepository.findByToken(token);
if (authToken != null) {
// Add user to SecurityContextHolder
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
authToken.getUser(), null, new ApplicationUserDetail(authToken.getUser()).getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
SecurityContextHolder.clearContext();
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment environment;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers(HttpMethod.POST, "/api/v1/login").permitAll()
.antMatchers(HttpMethod.POST, "/api/v1/user/register").permitAll().anyRequest().authenticated();
// Implementing Token based authentication in this filter
final TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter();
http.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(UrlConstant.BASE_ADMIN_URI_V1 + UrlConstant.CURRENCY_LIST_FOR_MARKET);
web.ignoring().antMatchers(UrlConstant.BASE_ADMIN_URI_V1 + UrlConstant.TRADING_FEES);
}
}
}
ApplicationUserDetail.java
public class ApplicationUserDetail implements UserDetails,Serializable {
private static final long serialVersionUID = 1L;
transient User user;
public ApplicationUserDetail(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole().getName());
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmailId();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return user.getIsEnabled();
}
}
More info: When i am printing response in my api it is correct but when i am printing it in my Authentication filter i got the response of fee api in my currency api so i think there is some problem between api to filter.
I want to pass the user object I use for authentication in a filter to the resource. Is it possible?
I'm using wildfly 10 (resteasy 3)
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Inject
private UserDao userDao;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
logger.warn("Filter");
String uid = requestContext.getHeaderString("Authorization");
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private User validateUser(String uid) {
return userDao.getById(uid);
}
}
There are two ways I could see to do this. The first is, perhaps, the more standard way but is also more code. Ultimately you'll inject the user as part of the request. However, the first thing you need for this solution is a Principal. A very simple one might be:
import java.security.Principal;
...
public class UserPrinicipal implements Prinicipal {
// most of your existing User class but needs to override getName()
}
Then, in your filter:
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return user;
}
#Override
public boolean isUserInRole(String role) {
// whatever works here for your environment
}
#Override
public boolean isSecure() {
return containerRequestContext.getUriInfo().getAbsolutePath().toString().startsWith("https");
}
#Override
public String getAuthenticationScheme() {
// again, whatever works
}
});
In the class where you want the User, you could do something like:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething() {
User user = (User)securityContext.getUserPrincipal();
}
}
I've implemented it this way and it works pretty well. However, an arguably simpler way is to just store the user in the session:
#Context
private HttpServletRequest request;
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
request.getSession().setAttribute("user", user);
Then, in your service:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething(#Context HttpServletRequest request) {
User user = (User)request.getSession().getAttribute("user");
}
}
The downside of the second method is that you are really no longer a stateless service as you're storing state somewhere. But the HttpSession is there even if you don't use it.
I want to implement my own authentication with spring. To keep things simple at first I'm going to implement the first step without any session, but an HTTP-Authorization header sent in every request.
I've read the documentation, many tutorials and of course searched on stackoverflow, but I couldn't fix it.
What I have is a filter (RequestFilter), which extracts the Authorization header and initializes the security context with an own Authentication (AuthenticationToken). Then there is an AuthenticationProvider (TokenAuthenticationProvider) supporting my AuthenticationToken. The filter is working, the authentication provider is not. None of the methods in TokenAuthenticationProvider are invoked. Can you help me, thanks :).
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOGGER = Logger.getLogger(SecurityConfig.class.getName());
#Autowired
TokenAuthenticationProvider tokenAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new RequestFilter(), BasicAuthenticationFilter.class)
.csrf().disable()
;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(tokenAuthenticationProvider)
;
}
}
#Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = Logger.getLogger(TokenAuthenticationProvider.class.getName());
#Autowired
ClientRepository clientRepository;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String authToken = authentication.getCredentials().toString();
LOGGER.severe("AUTH TOKEN: " + authToken);
return Optional.ofNullable(clientRepository.findByAuthToken(authToken))
.map((Client client) -> new AuthenticationToken(client.getId(), client.getAuthToken()))
.orElseThrow(() -> new AccessDeniedException(""));
}
#Override
public boolean supports(Class<?> authentication) {
LOGGER.severe(authentication.getName());
return AuthenticationToken.class.isAssignableFrom(authentication);
}
}
public class RequestFilter extends OncePerRequestFilter {
private final Logger LOGGER = Logger.getLogger(RequestFilter.class.getName());
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
LOGGER.severe("RequestFilter works");
Optional.ofNullable(request.getHeader("Authorization"))
.ifPresent((String token) -> SecurityContextHolder
.getContext()
.setAuthentication(new AuthenticationToken(token))
);
chain.doFilter(request, response);
}
}
public class AuthenticationToken extends AbstractAuthenticationToken {
private final String credentials;
private final Long principal;
public AuthenticationToken(String credentials) {
this(null, credentials);
setAuthenticated(false);
}
public AuthenticationToken(Long principal, String credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(true);
}
#Override
public Object getCredentials() {
return credentials;
}
#Override
public Object getPrincipal() {
return principal;
}
}
#RestController
public class HttpGateController {
private static final Logger LOGGER = Logger.getLogger(HttpGateController.class.getName());
#RequestMapping(
name="/gate",
method= RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE
)
public String gateAction(#RequestBody String request) {
Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getCredentials)
.map(ObjectUtils::nullSafeToString)
.ifPresent(LOGGER::severe);
return request;
}
#RequestMapping("/status")
public String statusAction() {
return "It works.";
}
}