Dependency Injection : Could not initialize class org.springframework.beans.CachedIntrospectionResults - java

I'm developping a Java library that uses Spring framework.
On this library I created a class that will act as application manager. It uses the singleton design pattern.
This class uses dependency injection for external HTTP calls.
public class SharePointManager {
private static SharePointManager instance = null;
private static IAuthenticationResult iAuthenticationResult;
private static String token;
private static SiteService siteService;
public static SharePointManager getInstance() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
SharePointManager.dummyService = context.getBean(DummyService.class);
SharePointManager.siteService = context.getBean(SiteService.class);
if(instance == null) {
instance = new SharePointManager();
}
return instance;
}
private SharePointManager() {
}
public SharePointSiteResponse getAllSites(SharePointCredentialRequest creds) throws Exception {
if( iAuthenticationResult != null && iAuthenticationResult.accessToken() != null ) {
token = iAuthenticationResult.accessToken();
if (Utils.checkToken(iAuthenticationResult.accessToken())) {
token = Utils.getToken(creds).accessToken();
}
} else {
token = Utils.getToken(creds).accessToken();
}
token = iAuthenticationResult.accessToken();
return siteService.getAllSites(creds, token);
}
}
In my Service layer I also want to do dependency injection with a HttpRequest class :
#Service
public class SiteServiceImpl implements SiteService {
private final HttpRequest httpRequest;
public SiteServiceImpl(HttpRequest httpRequest) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
this.httpRequest = context.getBean(HttpRequest.class);
}
#Override
public SharePointSiteResponse getAllSites(SharePointCredentialRequest credentialRequest, String token) throws Exception {
if(Utils.checkToken(token))
token = Utils.createToken(credentialRequest);
URL url = new URL("https://graph.microsoft.com/v1.0/sites?search=*");
return httpRequest.getAllSitesRequest(token, url);
}
}
In my httpRequest class i just build my calls:
#Component
public class HttpRequest {
private final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH").disableHtmlEscaping().create();
public SharePointSiteResponse getAllSitesRequest(String token, URL url)
throws IOException, RequestException {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
return getSiteConnection(token, url, conn);
}
....
My AppConfig class is just here for configuration:
#Configuration
#ComponentScan(basePackages = {"fr.dsidiff.sharepoint"})
public class AppConfig {
}
I obtain an error message :
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.springframework.beans.CachedIntrospectionResults
at org.springframework.context.support.AbstractApplicationContext.resetCommonCaches(AbstractApplicationContext.java:969)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93)
at fr.dsidiff.sharepoint.SharePointManager.getInstance(SharePointManager.java:20)

I tried two solutions that solved my issue in my service layer:
#Service
public class SiteServiceImpl implements SiteService {
private final HttpRequest httpRequest;
#Autowired
public SiteServiceImpl(HttpRequest httpRequest) {
this.httpRequest = httpRequest;
}
...
I also tried javax #Inject too and it works
I didn't expect i could inject using autowired...

Related

why spring boot #Value populating not working

I have a spring service:
#Service
public class AuthorizationServiceImpl implements AuthorizationService {
#Value("${local.address}")
private String localIP;
private final String LOCALHOST_IPV4 = "127.0.0.1";
private final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
#CrossOrigin(origins = "*")
#Override
public boolean isAuthorized(String token, HttpServletRequest request) {
try {
TokenUserModel user = new TokenUserModel();
user.setSessionID("");
user = getTokenUserModel(token);
IServiceAAA client = new ServiceAAA().getBasicHttpBindingIServiceAAA();
int systemId = client.getUserSystemId("InvoiceAdmin");
String xRealIP = request.getRemoteAddr();
String ipAddress = xRealIP.equals(LOCALHOST_IPV4) || xRealIP.equals(LOCALHOST_IPV6) ? localIP : xRealIP;
String userAgent = request.getHeader("User-Agent");
int response = client.checkPermissionAndUserData(user.getSessionID(), "Admin",
userAgent,
ipAddress, systemId, systemId, "");
return response == 0;
} catch (Exception ex) {
throw new AuthorizationException(AuthorizationError.ERR_WHILE_AUTHORISATION);
}
}
}
#Value("${local.address}")
private String localIP; - this line of code didn't working, not populating local.address field from application.properties:
local.address=10.10.16.13
Update:
I have 3 application properties file for spring profiles, named:
application.properties,application-dev.properties,application-prod.properties,
my current active profile is application-dev.properties
('spring.profiles.active=dev' - it's inside of my
application.properties file).
I'm sure that 'local.address'
property is defined in application-dev.properties
local.address=10.10.16.13
AuthorizationService is a just interface that AutheroziationServiceImpl impliments.
public interface AuthorizationService {
boolean isAuthorized(String token, HttpServletRequest request) throws UnsupportedEncodingException;
void logout(String token) throws UnsupportedEncodingException;
TokenUserModel getTokenUserModel(String token) throws UnsupportedEncodingException;
}
I'm using it in constructor of one of controllers:
#Autowired
public AuthorizationController(AuthorizationService authorizationService) {
this.authorizationService = authorizationService;
}
private final AuthorizationService authorizationService;
Yes, it's spring managed bean annotated as #Service

#Value is not injecting in SpringBoot Test Context

I'm using azure keyvault to pull my application properties. I'm using spring #value annotation to set the property value from the keyvault by placing the placeholder in the application.properties file. In my main application context I was able to pull the properties and test the application flow. Were as in test context its throwing some issuing saying vault properties aren't injected. Here is my properties bean class looks like, and the stack trace of the issue. I tried to mock the KeyVaultProperties in the ControllerTest class still having same issue.
KeyVault.java
#Data
#Component
public class KeyVaultProperties {
#Value("${by-pass-token}")
private String token;
#Value("${backend-clients}")
private String clients;
}
ControllerTest.java
#SpringBootTest
#SpringBootConfiguration
#AutoConfigureMockMvc
public class ControllerTest {
#Autowired
Controller controller;
#Autowired
private MockMvc mockMvc;
#Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
}
Controller.java
#RestController
#Slf4j
#RequestMapping("/api/test")
public class Controller {
#GetMapping(value = "/hello")
public String getString() {
return "Hello";
}
}
AuthConfiguration.java
#Slf4j
#Component
public class AuthConfiguration extends HandlerInterceptorAdapter {
#Autowired
private KeyVaultProperties keyVaultProperties;
private static final String CORRELATION_ID_LOG_VAR_NAME = "correlationId";
private static final String CORRELATION_ID_HEADER_NAME = "Correlation-Id";
#PostConstruct
public void setup() {
System.out.println("-------#PostConstruct------setup----------------");
sub = keyVaultProperties.getClients();
ByPass = keyVaultProperties.getAuthByPassToken();
}
#Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
throws Exception {
System.out.println("-------preHandle----------------------");
final Boolean isValidToken;
final String correlationId = getCorrelationIdFromHeader(request);
log.info("correlationId:{}",correlationId);
MDC.put(CORRELATION_ID_LOG_VAR_NAME, correlationId);
return true;
}
#Override
public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
final Object handler, final Exception ex) {
System.out.println("-------afterCompletion----------------------");
MDC.remove(CORRELATION_ID_LOG_VAR_NAME);
}
private String getCorrelationIdFromHeader(final HttpServletRequest request) {
String correlationId = request.getHeader(CORRELATION_ID_HEADER_NAME);
if (correlationId == null) {
correlationId = generateUniqueCorrelationId();
}
return correlationId;
}
}
app/src/main/resources/application.properties
by-pass-token = ${BY-PASS-TOKEN}
backend-clients = ${CLIENTS}
azure.keyvault.enabled=true
Stack Trace:
2021-04-04 13:28:03.640 [main] ERROR org.springframework.boot.SpringApplication - Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AuthConfiguration': Unsatisfied dependency expressed through field 'KeyVaultProperties'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'KeyVaultProperties': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'by-pass-token' in value "${by-pass-token}"
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject
You could set the value of properties to Azure Key Vault by authenticating via Azure AD.
Note: In order for your application to have access to the Key Vault contents, you must set the appropriate permissions for your application in the Key Vault. Navigate to Azure Key Vault > Access Policies > Add access policy > select your application in select principal.
Dependencies:
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-keyvault</artifactId>
<version>1.0.0</version>
</dependency>
Connect to key vault via AzureAD based on client credentials flow:
public class ClientSecretKeyVaultCredential extends KeyVaultCredentials
{
private String clientId;
private String clientKey;
public ClientSecretKeyVaultCredential( String clientId, String clientKey ) {
this.clientId = clientId;
this.clientKey = clientKey;
}
#Override
public String doAuthenticate(String authorization, String resource, String scope) {
AuthenticationResult token = getAccessTokenFromClientCredentials(
authorization, resource, clientId, clientKey);
return token.getAccessToken();
}
private static AuthenticationResult getAccessTokenFromClientCredentials(
String authorization, String resource, String clientId, String clientKey) {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(authorization, false, service);
ClientCredential credentials = new ClientCredential(clientId, clientKey);
Future<AuthenticationResult> future = context.acquireToken(
resource, credentials, null);
result = future.get();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
service.shutdown();
}
if (result == null) {
throw new RuntimeException("authentication result was null");
}
return result;
}
}
Access Key vault:
You could use client.setSecret("Secret-Name", "value") to set your properties.
// ClientSecretKeyVaultCredential is the implementation of KeyVaultCredentials
KeyVaultClient client = new KeyVaultClient(
new ClientSecretKeyVaultCredential(clientId, clientKey));
// KEYVAULT_URL is the location of the keyvault to use: https://<yourkeyvault>.vault.azure.net
SecretBundle secret = client.getSecret( KEYVAULT_URL, "Secret-name" );
log( secret.value() );

Jersey 2.22.2 with wildfly 9.0.2Final Authentication filter deployment error

So I created a custom AuthenticationFilter for a Jersey Rest service. Locally it deploys and works as intended but when deployed to our actual test server I get the below error when starting Wildfly server.
Caused by: A MultiException has 3 exceptions. They are:
1. java.lang.IllegalStateException: A descriptor SystemDescriptor( implementation=org.glassfish.jersey.server.internal.process.ServerProcess>ingBinder$UriRoutingContextFactory
contracts={org.glassfish.jersey.server.ExtendedUriInfo,javax.ws.rs.core.UriInfo,javax.ws.rs.container.ResourceInfo}
scope=org.glassfish.jersey.process.internal.RequestScoped
qualifiers={}
descriptorType=PROVIDE_METHOD
descriptorVisibility=NORMAL
metadata=
rank=0
loader=org.glassfish.hk2.utilities.binding.AbstractBinder$2#19fb4349
proxiable=true
proxyForSameScope=false
analysisName=null
id=16
locatorId=5
identityHashCode=481594768
reified=true) requires a proxy, but the proxyable library is not on the classpath
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.lanyon.rest.service.filters.AuthenticationFilter errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on com.lanyon.rest.service.filters.AuthenticationFilter
Caused by: java.lang.IllegalStateException: A descriptor SystemDescriptor(
implementation=org.glassfish.jersey.server.internal.process.ServerProcess>ingBinder$UriRoutingContextFactory
contracts={org.glassfish.jersey.server.ExtendedUriInfo,javax.ws.rs.core.UriInfo,javax.ws.rs.container.ResourceInfo}
scope=org.glassfish.jersey.process.internal.RequestScoped
qualifiers={}
descriptorType=PROVIDE_METHOD
descriptorVisibility=NORMAL
metadata=
rank=0
loader=org.glassfish.hk2.utilities.binding.AbstractBinder$2#19fb4349
proxiable=true
proxyForSameScope=false
analysisName=null
id=16
locatorId=5
identityHashCode=481594768
reified=true) requires a proxy, but the proxyable library is not on the classpath"}}}}
Here is the Auth filter:
#Provider
public class AuthenticationFilter implements javax.ws.rs.container.ContainerRequestFilter {
private static final String AUTHORIZATION_PROPERTY = "Authorization";
private static final String AUTHENTICATION_SCHEME = "Basic";
private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED)
.entity("Access Denied").build();
private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN)
.entity("Forbidden").build();
private static final String WS_CREDENTIALS_KEY = "DEMAND_WEBSERVICE_CREDENTIALS";
private static final String DEFAULT_DAO_BEAN_NAME = "exchangeDefaultsDAO";
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) {
Method method = getResourceMethod();
if (isAnnotationPresent(method, PermitAll.class)) {
return;
}
if (isAnnotationPresent(method, DenyAll.class)) {
requestContext.abortWith(ACCESS_FORBIDDEN);
return;
}
final List<String> authorization = getAuthorizationHeaders(requestContext);
if (authorization == null || authorization.isEmpty()) {
requestContext.abortWith(ACCESS_DENIED);
return;
}
String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
final String authToken = new String(encodedUserPassword.getBytes());
if (isAnnotationPresent(method, RolesAllowed.class)) {
RolesAllowed rolesAnnotation = getRolesAllowedAnnotation(method);
Set<String> rolesSet = new HashSet<>(Arrays.asList(rolesAnnotation.value()));
if (!isUserAllowed(authToken, rolesSet)) {
requestContext.abortWith(ACCESS_DENIED);
return;
}
}
}
private boolean isUserAllowed(final String authToken, final Set<String> rolesSet) {
String[] webServiceCredentials = getWebServiceCredentials();
if (webServiceCredentials.length != 2) {
return false;
}
return authToken.equals(webServiceCredentials[0]) && rolesSet.contains(webServiceCredentials[1]);
}
private String[] getWebServiceCredentials() {
return getExchangeDefaultsBean().getDefault(WS_CREDENTIALS_KEY).split("\\|");
}
private Method getResourceMethod() {
return resourceInfo.getResourceMethod();
}
private List<String> getAuthorizationHeaders(ContainerRequestContext requestContext) {
final MultivaluedMap<String, String> headers = requestContext.getHeaders();
final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);
return authorization;
}
private boolean isAnnotationPresent(Method method, Class<? extends Annotation> annotation) {
return method.isAnnotationPresent(annotation);
}
private RolesAllowed getRolesAllowedAnnotation(Method method) {
return method.getAnnotation(RolesAllowed.class);
}
private ExchangeDefaultsDAO getExchangeDefaultsBean() {
return (ExchangeDefaultsDAO)ApplicationContextProvider.getApplicationContext().getBean(DEFAULT_DAO_BEAN_NAME);
}
}
Then the Jersey Application:
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
register(AuthenticationFilter.class);
register(TestAPIService.class);
}
}
Finally the Rest service:
#Path("/somepathname")
public class TestAPIService {
#RolesAllowed("API_ADMIN")
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response getRejectReasons() {
return Response.status(Response.Status.OK).entity("Working as expected!").build();
}
}
I have added javaassist to my classpath but that did nothing. Any help you can provide would be greatly appreciated.
Cheers!

Configure custom OAuth2AccessToken on a client Spring Boot Application

The standard JSON format that an authorization server usually gives you, has a property named "expires_in", but now I'm working with an autorization server that gives me a property named "access_token_expires_in". Because of this, my OAuth2AccessToken always returns isExpired to false even when then access_token is expired, and that makes sens because it's trying to read the "expires_in" property that dose not exist. The getAdditionalInformation from OAuth2AccessToken returns my "access_token_expires_in" property value with 18000.
I was wondering if I can tell spring to use the "access_token_expires_in" property as an expiration value for my access_token?
My code:
#Configuration
class OAuth2RestConfiguration {
#Bean
protected OAuth2ProtectedResourceDetails resource() {
final ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setAccessTokenUri("<tokenUri>");
resourceDetails.setClientId("<clientId>");
resourceDetails.setClientSecret("<clientSecret>");
return resourceDetails;
}
#Bean
public OAuth2RestTemplate restTemplate() throws Exception {
final AccessTokenRequest atr = new DefaultAccessTokenRequest();
final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource(),
new DefaultOAuth2ClientContext(atr));
return oAuth2RestTemplate;
}
}
Authorization server response sample:
{
"refresh_token_expires_in": 0,
"access_token": "<access_token>",
"access_token_expires_in": 18000,
"token_type": "bearer"
}
EDIT 1:
As a workaround, I've extended the OAuth2RestTemplate class and override the getAccessToken method:
public class CustomOAuth2RestTemplate extends OAuth2RestTemplate {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomOAuth2RestTemplate.class);
private OAuth2ClientContext context;
private Long LAST_RESET = getCurrentTimeSeconds();
private Long FORCE_EXPIRATION;
public CustomOAuth2RestTemplate(OAuth2ProtectedResourceDetails resource) {
super(resource);
this.context = super.getOAuth2ClientContext();
this.FORCE_EXPIRATION = 10800L;
}
public CustomOAuth2RestTemplate(OAuth2ProtectedResourceDetails resource,
DefaultOAuth2ClientContext defaultOAuth2ClientContext, Long forceExpiration) {
super(resource, defaultOAuth2ClientContext);
this.context = defaultOAuth2ClientContext;
this.FORCE_EXPIRATION = Objects.requireNonNull(forceExpiration, "Please set expiration!");
}
#Override
public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException {
OAuth2AccessToken accessToken = context.getAccessToken();
final Long diff = getCurrentTimeSeconds() - LAST_RESET;
/*
Either use a hardcoded variable or use the value stored in
context.getAccessToken().getAdditionalInformation().
*/
if (diff > FORCE_EXPIRATION) {
LOGGER.info("Access token has expired! Generating new one...");
this.LAST_RESET = getCurrentTimeSeconds();
context.setAccessToken(null);
accessToken = acquireAccessToken(context);
} else {
accessToken = super.getAccessToken();
}
LOGGER.info("Access token: " + context.getAccessToken().getValue());
return accessToken;
}
private Long getCurrentTimeSeconds() {
return System.currentTimeMillis() / 1000L;
}
}
And now the bean:
#Bean
public OAuth2RestTemplate restTemplate() throws Exception {
final AccessTokenRequest atr = new DefaultAccessTokenRequest();
final OAuth2RestTemplate oAuth2RestTemplate = new CustomOAuth2RestTemplate(resource(),
new DefaultOAuth2ClientContext(atr), 10800L); //example: 3h
oAuth2RestTemplate.setRequestFactory(customRequestFactory());
return oAuth2RestTemplate;
}
EDIT 2:
After I analyzed the OAuth2RestTemplate class more thoroughly, code refactoring was required:
public class CustomOAuth2RestTemplate extends OAuth2RestTemplate {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomOAuth2RestTemplate.class);
private Long LAST_RESET = getCurrentTimeSeconds();
private Long FORCE_EXPIRATION;
public CustomOAuth2RestTemplate(OAuth2ProtectedResourceDetails resource) {
super(resource);
this.FORCE_EXPIRATION = 10800L; //3h
}
public CustomOAuth2RestTemplate(OAuth2ProtectedResourceDetails resource,
DefaultOAuth2ClientContext defaultOAuth2ClientContext, Long forceExpiration) {
super(resource, defaultOAuth2ClientContext);
this.FORCE_EXPIRATION = Objects.requireNonNull(forceExpiration, "Please set expiration!");
}
#Override
public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException {
final Long diff = getCurrentTimeSeconds() - LAST_RESET;
/*
Either use a hardcoded variable or use the value stored in
context.getAccessToken().getAdditionalInformation().
*/
if (diff > FORCE_EXPIRATION) {
LOGGER.info("Access token has expired! Generating new one...");
this.LAST_RESET = getCurrentTimeSeconds();
final OAuth2ClientContext oAuth2ClientContext = getOAuth2ClientContext();
oAuth2ClientContext.setAccessToken(null);
return acquireAccessToken(oAuth2ClientContext);
}
return super.getAccessToken();
}
private Long getCurrentTimeSeconds() {
return System.currentTimeMillis() / 1000L;
}
}
You can add custom parameter by implementing TokenEnhancer interface and overriding its method as follows:
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
// additionalInfo.put("CUSTOM_PARAM1", "CUSTOM_VALUE1");
additionalInfo.put("username", authentication.getPrincipal());//adding username param
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends
AuthorizationServerConfigurerAdapter {
#Override
public void configure(final AuthorizationServerEndpointsConfigurer
endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints.tokenStore(tokenStore)
.tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
Hope it helps!
I'll post this as an answer since it works just fine. The only thing that was added was synchronized for concurrency, so that multiple access tokens should never be requested.
Final code:
public class CustomOAuth2RestTemplate extends OAuth2RestTemplate {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomOAuth2RestTemplate.class);
private Long LAST_RESET = getCurrentTimeSeconds();
private Long FORCE_EXPIRATION;
public CustomOAuth2RestTemplate(OAuth2ProtectedResourceDetails resource) {
super(resource);
this.FORCE_EXPIRATION = 10800L; // 3h
}
public CustomOAuth2RestTemplate(OAuth2ProtectedResourceDetails resource,
DefaultOAuth2ClientContext defaultOAuth2ClientContext, Long forceExpiration) {
super(resource, defaultOAuth2ClientContext);
this.FORCE_EXPIRATION = Objects.requireNonNull(forceExpiration, "Please set expiration!");
}
#Override
public synchronized OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException {
final Long diff = getCurrentTimeSeconds() - LAST_RESET;
/*
* Either use a hardcoded variable or use the value stored in
* context.getAccessToken().getAdditionalInformation().
*/
if (diff > FORCE_EXPIRATION) {
LOGGER.info("Access token has expired! Generating new one...");
this.LAST_RESET = getCurrentTimeSeconds();
final OAuth2ClientContext oAuth2ClientContext = getOAuth2ClientContext();
oAuth2ClientContext.setAccessToken(null);
return acquireAccessToken(oAuth2ClientContext);
}
return super.getAccessToken();
}
private Long getCurrentTimeSeconds() {
return System.currentTimeMillis() / 1000L;
}
}

How to make Shiro return 403 Forbidden with Spring Boot rather than redirect to login.jsp

I have a server that is just an API endpoint, no client front-end, no jsp, no html. It uses Spring Boot and I'm trying to secure it with Shiro. The relevent parts of my SpringBootServletInitializer look like this. I'm trying to get Shiro to return a 403 response if it fails the roles lookup as defined in BasicRealm. Yet it seems to default to redirecting to a non-existent login.jsp and no matter what solution I seem to use. I can't override that. Any help would be greatly appreciated.
#SpringBootApplication
public class RestApplication extends SpringBootServletInitializer {
...
#Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
Map<String, String> filterChain = new HashMap<>();
filterChain.put("/admin/**", "roles[admin]");
shiroFilter.setFilterChainDefinitionMap(filterChain);
shiroFilter.setSecurityManager(securityManager());
return shiroFilter;
}
#Bean
public org.apache.shiro.mgt.SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
CookieRememberMeManager rmm = new CookieRememberMeManager();
rmm.setCipherKey(Base64.decode("XXXXXXXXXXXXXXXXXXXXXX"));
securityManager.setRememberMeManager(rmm);
return securityManager;
}
#Bean(name = "userRealm")
#DependsOn("lifecycleBeanPostProcessor")
public BasicRealm userRealm() {
return new BasicRealm();
}
#Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
public class BasicRealm extends AuthorizingRealm {
private static Logger logger = UserService.logger;
private static final String REALM_NAME = "BASIC";
public BasicRealm() {
super();
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token)
throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String userid = upToken.getUsername();
User user = Global.INST.getUserService().getUserById(userid);
if (user == null) {
throw new UnknownAccountException("No account found for user [" + userid + "]");
}
return new SimpleAuthenticationInfo(userid, user.getHashedPass().toCharArray(), REALM_NAME);
}
#Override
protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
String userid = (String) principals.getPrimaryPrincipal();
if (userid == null) {
return new SimpleAuthorizationInfo();
}
return new SimpleAuthorizationInfo(Global.INST.getUserService().getRoles(userid));
}
}
OK, here is how I solved it. I created a class ...
public class AuthFilter extends RolesAuthorizationFilter {
private static final String MESSAGE = "Access denied.";
#Override
protected boolean onAccessDenied(final ServletRequest request, final ServletResponse response) throws IOException {
HttpServletResponse httpResponse ;
try {
httpResponse = WebUtils.toHttp(response);
}
catch (ClassCastException ex) {
// Not a HTTP Servlet operation
return super.onAccessDenied(request, response) ;
}
if (MESSAGE == null) {
httpResponse.sendError(403);
} else {
httpResponse.sendError(403, MESSAGE);
}
return false; // No further processing.
}
}
... and then in my shiroFilter() method above I added this code ...
Map<String, Filter> filters = new HashMap<>();
filters.put("roles", new AuthFilter());
shiroFilter.setFilters(filters);
... hope this helps someone else.
In Shiro 1.4+ you can set the login url in your application.properties:
https://github.com/apache/shiro/blob/master/samples/spring-boot-web/src/main/resources/application.properties#L20
Earlier versions you should be able to set ShiroFilterFactoryBean.setLoginUrl("/login")
https://shiro.apache.org/static/current/apidocs/org/apache/shiro/spring/web/ShiroFilterFactoryBean.html

Categories