I am writing a Spring boot application and I have a question regarding my controller class. I read the documentation and saw that the controller is a singleton by deafult, so I have decided to save my user info in a DB and save it by session. I use the spring boot sesison managment JDBC. My question is though, if a method is also singelton. I know it seems like a foolish question but I am starting to second guess myself. I am confused if the code I have would fail if multiple users logged in with a session. Also, can I not have any variables in my methods, or should I move the logic I have here somewhere else. I really don't know.
Java Controller Method:
#RequestMapping(value = "/edit")
public ModelAndView editTab(#RequestParam(value = "currentAppcode", required = false, defaultValue = "Appcode") String currentAppcode,
#RequestParam(value = "currentAcro", required = false, defaultValue = "Arco") String currentAcro,
#RequestParam(value = "currentAppname", required = false, defaultValue = "Appname") String currentAppname,
HttpSession session) {
ModelAndView modelAndView = new ModelAndView();
private List<Some_Object> allQueryConfig = new ArrayList<Some_Object>();
session.setAttribute("SELECTED_APP",queryService.findDtoById(currentCode));
session.setAttribute("ALL_SERVERS", queryService.serverWork() );
allSystems.clear();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
try {
OPTApplication selected_app = (OPTApplication) session.getAttribute("SELECTED_APP");
rc.setProxy();
if (rc.validateUser(currentPrincipalName, selected_app.getSys_id()) != 0 || queryService.isAuditor(currentPrincipalName)) {
session.setAttribute("GP_SELECTED", apiRunner.runGPData(selected_app.getNumber()));
allQueryConfig = queryService.getNonDistinctServerInfo();
modelAndView.setViewName("create");
for (AppSelectorTier current : allQueryConfig) {
allSystems.add(current.getSystem());
}
} else {
modelAndView.setViewName("forbidden");
}
} catch (Exception e) {
e.printStackTrace();
modelAndView.setViewName("test");
}
modelAndView.addObject("currentAppName", "Application: " + currentAppname);
modelAndView.addObject("ary4", queryService.getServerInfo());
modelAndView.addObject("adid", currentPrincipalName);
modelAndView.addObject("hasAccess", false);
return modelAndView;
}
Another example:
public #ResponseBody List<SelectorDTO> getGPdataTaddm(#RequestBody TestApp mTestApp, HttpServletRequest request, HttpSession session)
throws SQLException {
GroupPatternDTO gp_selected = (GroupPatternDTO) session.getAttribute("GP_SELECTED");
session.setAttribute("QUERY_CONFIG_DATA", new QueryConfigData());
QueryConfigData query_config_data = (QueryConfigData) session.getAttribute("QUERY_CONFIG_DATA");
List<SelectorDTO> inProgressInfo = queryService.getInfoFromInProgress(gp_selected.getCode());
if (inProgressInfo.size() != 0) {
gp_selected.setSels(inProgressInfo);
} else {
for (SelectorDTO current : gp_selected.getSels()) {
String objectPassed = current.getDescription().replaceAll("[^a-zA-Z0-9]", "");
System.out.println("First match: ----------------------------------------------------------------------"
+ objectPassed);
if (queryService.getTierTypeFromObj(objectPassed).size() != 0) {
AppSelectorTier selectorObj = queryService.getTierTypeFromObj(taddmObjectPassed).get(0);
query_config_data.setName(selectorObj.getName());
query_config_data.setSystem(selectorObj.getSystem());
query_config_data.setMqlList(selectorObj.getMqllisting());
query_config_data.setTag1(selectorObj.getTagname_one());
query_config_data.setTag2(selectorObj.getTagname_two());
query_config_data.setMqlSelector(selectorObj.getMqllisting());
query_config_data.setParseInstruc(selectorObj.getParseinstruction());
query_config_data.setTaddmObject(selectorObj.getTaddmobj());
}
}
}
session.setAttribute("GP_SELECTED", gp_selected);
session.setAttribute("QUERY_CONFIG_DATA", query_config_data);
return gp_selected.getSels();
}
I am unsure if this is a good pracice or if for my usecase this is fine?
Related
I want to implement a feature that user connects his account with external applications (similar feature is in Facebook). User has to log in to external application and grant permission to access data by my application.
Once user connected an external app, data will be exchanged in background using access and refresh tokens.
Application architecture is:
SPA front-end (Angular)
REST API (Spring), multiple nodes
ScyllaDB
Envoy proxy (with JWT verification)
The first idea is to use Spring OAuth2 Client. However, some changes need to be made:
there is no Principal because JWT is verified by Envoy proxy and X-USER-ID header is added
REST API is stateless and we shouldn't store authorization code in session
even with sessions, there are multiple nodes and we need to share authorization code between nodes
custom URL, e.g. /app_name/connect instead of /oauth2/authorization/app_name
redirect URL may be invalid (but it's verified by Spring's filter)
How this could work:
user click "Connect with app" in SPA
SPA redirects user to /oauth2/authorization/app_name (or custom URL)
Spring redirects user to external app's authentication server
user authenticates and grants permissions
external app redirects user back to Spring (or straight to SPA?)
Spring redirects user back to SPA (or SPA sends access token to REST API?)
Despite Spring Security components can be replaced, many of them are coupled and you need to rewrite OAuth2 Client flow almost from scratch. Maybe I'm doing something wrong and it can be achieved easier.
What I already did:
http
.cors().and()
.csrf().disable()
.authorizeRequests().anyRequest().permitAll().and()
.oauth2Client(); // get rid of these two filters?
#Configuration
#RequiredArgsConstructor
public class OAuth2ClientConfig {
private final CassandraTemplate cassandraTemplate;
// overriding original client service - we need to store tokens in database
#Bean
public OAuth2AuthorizedClientService authorizedClientService(
CassandraTemplate cassandraTemplate,
ClientRegistrationRepository clientRegistrationRepository) {
return new ScyllaOAuth2AuthorizedClientService(cassandraTemplate, clientRegistrationRepository);
}
// configure client provider to use authorization code with refresh token
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
var authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.build();
var authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository,
authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
// the specs recommend to use WebClient for exchanging data instead of RestTemplate
#Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
// override request repository - and I'm stuck there
#Bean
public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() {
return new ScyllaOAuth2AuthorizationRequestRepository(cassandraTemplate);
}
}
Because there are multiple nodes of REST API, we can't use sessions. We need to store request somewhere, e.g. ScyllaDB, Redis, Hazelcast, etc. I decided to store it as JSON in ScyllaDB but I ran into trouble.
#Slf4j
#RequiredArgsConstructor
public final class ScyllaOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
private final CassandraTemplate cassandraTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
#Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
Assert.notNull(request, "request cannot be null");
var stateParameter = this.getStateParameter(request);
if (stateParameter == null) {
return null;
}
return this.getAuthorizationRequest(request, stateParameter);
}
#Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request,
HttpServletResponse response) {
Assert.notNull(request, "request cannot be null");
Assert.notNull(response, "response cannot be null");
if (authorizationRequest == null) {
this.removeAuthorizationRequest(request, response);
return;
}
var state = authorizationRequest.getState();
var userId = UUID.fromString(request.getHeader(Constants.USER_ID));
Assert.hasText(state, "authorizationRequest.state cannot be empty");
try {
// serialization of Auth2AuthorizationRequest to JSON works
cassandraTemplate.getCqlOperations().execute("insert into oauth2_requests (user_id,state,data) values (?,?,?)",
userId, state, objectMapper.writeValueAsString(authorizationRequest));
} catch (JsonProcessingException e) {
log.warn("Unable to save authorization request", e);
}
}
#Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
Assert.notNull(request, "request cannot be null");
var stateParameter = this.getStateParameter(request);
if (stateParameter == null) {
return null;
}
var userId = UUID.fromString(request.getHeader(Constants.USER_ID));
var originalRequest = this.getAuthorizationRequest(request, stateParameter);
cassandraTemplate.getCqlOperations().execute("delete from oauth2_requests where user_id=? and state=?",
userId, stateParameter);
return originalRequest;
}
private String getStateParameter(HttpServletRequest request) {
return request.getParameter(OAuth2ParameterNames.STATE);
}
private UUID getUserId(HttpServletRequest request) {
return UUID.fromString(request.getHeader(Constants.USER_ID));
}
private OAuth2AuthorizationRequest getAuthorizationRequest(HttpServletRequest request, String state) {
var userId = getUserId(request);
var jsonRequest = cassandraTemplate.getCqlOperations().queryForObject(
"select data from oauth2_requests where user_id=? and state=?", String.class, userId, state);
if (StringUtils.isNotBlank(jsonRequest)) {
try {
// trying to mess with OAuth2ClientJackson2Module
var objectMapper = new Jackson2ObjectMapperBuilder().autoDetectFields(true)
.autoDetectGettersSetters(true)
.modules(new OAuth2ClientJackson2Module())
.visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
.build();
return objectMapper.readValue(jsonRequest, OAuth2AuthorizationRequest.class);
} catch (JsonProcessingException e) {
log.warn("Error decoding authentication request", e);
}
}
return null;
}
}
I get error when trying to deserialize JSON to OAuth2AuthorizationRequest:
Missing type id when trying to resolve subtype of [simple type, class org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest]: missing type id property '#class'
Without adding OAuth2ClientJackson2Module there is another error:
Cannot construct instance of `org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
By the way, OAuth2ClientJackson2Module seems never used in original filters.
Maybe it's better to serialize this object Java way and store it as BLOB or do not store request in database but somewhere other.
Another part is the controller action:
// it had to be /apps/app_name/connect but in Spring OAuth2 Client it's hardcoded to append provider name at the end
#GetMapping("/apps/connect/app_name")
public void connect(HttpServletRequest request, HttpServletResponse response) throws IOException {
userAppService.authorize(request, response, "app_name");
}
To get rid of filters which verify redirect URL and have many things hardcoded:
#Service
#RequiredArgsConstructor
public class UserAppService {
private final HttpSecurity httpSecurity;
private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private final AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private final ClientRegistrationRepository clientRegistrationRepository;
private final OAuth2AuthorizedClientManager authorizedClientManager;
private final OAuth2AuthorizedClientRepository authorizedClientRepository;
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public void authorize(HttpServletRequest request, HttpServletResponse response, String appName) throws IOException {
var userId = UUID.fromString(request.getHeader(Constants.USER_ID));
var authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(appName)
.principal(UUIDPrincipal.fromUserId(userId))
.build();
if (isAuthorizationResponse(request)) {
var authorizationRequest = this.authorizationRequestRepository.loadAuthorizationRequest(request);
if (authorizationRequest != null) {
processAuthorizationRequest(request, response);
}
} else {
try {
OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(authorizeRequest);
if (authorizedClient != null) {
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
System.out.println(accessToken);
}
} catch (ClientAuthorizationException e) {
// in this URL provider name is appended at the end and no way to change this behavior
var authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
"/apps/connect");
var authorizationRequest = authorizationRequestResolver.resolve(request);
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
this.redirectStrategy.sendRedirect(request, response, authorizationRequest.getAuthorizationRequestUri());
}
}
}
private void processAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
var authorizationRequest = this.authorizationRequestRepository.removeAuthorizationRequest(request, response);
var registrationId = (String) authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
var clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
var params = toMultiMap(request.getParameterMap());
var redirectUri = UrlUtils.buildFullRequestUrl(request);
var authorizationResponse = convert(params, redirectUri);
var authenticationRequest = new OAuth2AuthorizationCodeAuthenticationToken(
clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
OAuth2AuthorizationCodeAuthenticationToken authenticationResult;
try {
var authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
authenticationResult = (OAuth2AuthorizationCodeAuthenticationToken) authenticationManager
.authenticate(authenticationRequest);
} catch (OAuth2AuthorizationException ex) {
OAuth2Error error = ex.getError();
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(authorizationRequest.getRedirectUri())
.queryParam(OAuth2ParameterNames.ERROR, error.getErrorCode());
if (!StringUtils.hasText(error.getDescription())) {
uriBuilder.queryParam(OAuth2ParameterNames.ERROR_DESCRIPTION, error.getDescription());
}
if (!StringUtils.hasText(error.getUri())) {
uriBuilder.queryParam(OAuth2ParameterNames.ERROR_URI, error.getUri());
}
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build().encode().toString());
return;
}
// just copy-paste of original filter - trying to understand what's happening there
Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();
String principalName = (currentAuthentication != null) ? currentAuthentication.getName() : "anonymousUser";
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(), principalName, authenticationResult.getAccessToken(),
authenticationResult.getRefreshToken());
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, currentAuthentication, request,
response);
String redirectUrl = authorizationRequest.getRedirectUri();
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
}
private static boolean isAuthorizationResponse(HttpServletRequest request) {
return isAuthorizationResponseSuccess(request) || isAuthorizationResponseError(request);
}
private static boolean isAuthorizationResponseSuccess(HttpServletRequest request) {
return StringUtils.hasText(request.getParameter(OAuth2ParameterNames.CODE))
&& StringUtils.hasText(request.getParameter(OAuth2ParameterNames.STATE));
}
private static boolean isAuthorizationResponseError(HttpServletRequest request) {
return StringUtils.hasText(request.getParameter(OAuth2ParameterNames.ERROR))
&& StringUtils.hasText(request.getParameter(OAuth2ParameterNames.STATE));
}
// copy paste - not tested this code yet
static MultiValueMap<String, String> toMultiMap(Map<String, String[]> map) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(map.size());
map.forEach((key, values) -> {
if (values.length > 0) {
for (String value : values) {
params.add(key, value);
}
}
});
return params;
}
static OAuth2AuthorizationResponse convert(MultiValueMap<String, String> request, String redirectUri) {
String code = request.getFirst(OAuth2ParameterNames.CODE);
String errorCode = request.getFirst(OAuth2ParameterNames.ERROR);
String state = request.getFirst(OAuth2ParameterNames.STATE);
if (StringUtils.hasText(code)) {
return OAuth2AuthorizationResponse.success(code).redirectUri(redirectUri).state(state).build();
}
String errorDescription = request.getFirst(OAuth2ParameterNames.ERROR_DESCRIPTION);
String errorUri = request.getFirst(OAuth2ParameterNames.ERROR_URI);
return OAuth2AuthorizationResponse.error(errorCode)
.redirectUri(redirectUri)
.errorDescription(errorDescription)
.errorUri(errorUri)
.state(state)
.build();
}
}
Client service to stored authorized clients in database:
#RequiredArgsConstructor
public class ScyllaOAuth2AuthorizedClientService implements OAuth2AuthorizedClientService {
private final CassandraTemplate cassandraTemplate;
private final ClientRegistrationRepository clientRegistrationRepository;
#Override
#SuppressWarnings("unchecked")
public OAuth2AuthorizedClient loadAuthorizedClient(String clientRegistrationId, String principal) {
var id = BasicMapId.id("userId", principal).with("appCode", clientRegistrationId);
var userApp = cassandraTemplate.selectOneById(id, UserApp.class);
if (userApp != null) {
var clientRegistration = getClientRegistration(clientRegistrationId);
var accessToken = getAccessToken(userApp);
var refreshToken = getRefreshToken(userApp);
return new OAuth2AuthorizedClient(clientRegistration, principal, accessToken, refreshToken);
} else {
return null;
}
}
#Override
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
Assert.notNull(principal, "principal cannot be null");
var userApp = new UserApp();
userApp.setUserId((UUID) principal.getPrincipal());
userApp.setAppCode(authorizedClient.getClientRegistration().getClientId());
if (authorizedClient.getAccessToken() != null) {
userApp.setAccessToken(authorizedClient.getAccessToken().getTokenValue());
userApp.setAccessTokenType(OAuth2AccessToken.TokenType.BEARER.getValue());
userApp.setAccessTokenScopes(authorizedClient.getAccessToken().getScopes());
userApp.setAccessTokenIssuedAt(authorizedClient.getAccessToken().getIssuedAt());
userApp.setAccessTokenExpiresAt(authorizedClient.getAccessToken().getExpiresAt());
}
if (authorizedClient.getRefreshToken() != null) {
userApp.setRefreshToken(authorizedClient.getRefreshToken().getTokenValue());
userApp.setRefreshTokenIssuedAt(authorizedClient.getRefreshToken().getIssuedAt());
userApp.setRefreshTokenExpiresAt(authorizedClient.getRefreshToken().getExpiresAt());
}
cassandraTemplate.insert(userApp);
}
#Override
public void removeAuthorizedClient(String clientRegistrationId, String principal) {
var id = BasicMapId.id("userId", principal).with("appCode", clientRegistrationId);
cassandraTemplate.deleteById(id, UserApp.class);
}
private ClientRegistration getClientRegistration(String clientRegistrationId) {
var clientRegistration = this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId);
if (clientRegistration == null) {
throw new DataRetrievalFailureException(
"The ClientRegistration with id '" + clientRegistrationId + "' exists in the data source, "
+ "however, it was not found in the ClientRegistrationRepository.");
}
return clientRegistration;
}
private OAuth2AccessToken getAccessToken(UserApp userApp) {
return new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER,
userApp.getAccessToken(),
userApp.getAccessTokenIssuedAt(),
userApp.getAccessTokenExpiresAt(),
userApp.getAccessTokenScopes());
}
private OAuth2RefreshToken getRefreshToken(UserApp userApp) {
return new OAuth2RefreshToken(userApp.getRefreshToken(), userApp.getRefreshTokenIssuedAt());
}
}
Too much code overwrite. I need to make it as simple as possible.
Currently I'm struggling with storing authorize request in database.
How to do it Spring way but to keep the app architecture given at the beginning of this question?
Any way to configure OAuth2 Client without hardcoded URL like /oauth2/authorization/provider_name?
Maybe it's better to do the whole OAuth2 flow client-side (within SPA) and the SPA should send access and request token to REST API (to store the tokens in order to be able to exchange data with external app)?
In OAuth2 wording, REST APIs are resource-servers, not clients.
What you can do is have
your proxy be transparent to OAuth2 (forward requests with their JWT access-token authorization header and responses status code)
configure each REST API as resource-server. Tutorials there: https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials.
add an OAuth2 client library to your Angular app to handle tokens and authorize requests. My favorite is angular-auth-oidc-client
probably use an intermediate authorization-server for identity federation (Google, Facebook, etc., but also internal DB, LDAP, or whatever is needed), roles management, MFA,... Keycloak is a famous "on premise" solution, but you can search for "OIDC authorization-server" in your favorite search engine and have plenty of alternate choices, including SaaS like Auth0 or Amazon Cognito.
This is fully compatible with distributed architectures and micro-services (session-less is the default configuration for resource-servers in the tutorials I linked).
Two cases for a micro-service delegating some of its processing to another resource-server:
the "child" request is made on behalf of the user who initiated the request => retrieve original access-token from Authentication instance in security-context and forward it (set it as Bearer authorization header for the sub-request)
the "child" request is not made on behalf of a user => client-credentials must be used (the micro-services acquires a new access-token in its own name to authorize the sub request). Refer to spring-boot-oauth2-client and your preferred REST client docs for details (WebClient, #FeignClient, RestTemplate).
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I have this method which I want to test using mockito
public boolean verifyState(HttpServletRequest request) {
String stateToken = getCookieByName(request, STATE_TOKEN);
String authToken = getCookieByName(request, AUTHN);
boolean isValidState = !stateToken.isEmpty() && !authToken.isEmpty();
if (isValidState) {
return true;
}
else {
return false;
}
}
It does two calls to getCookieName(), which has this implementation.
public String getCookieByName(HttpServletRequest request, String cookieName) {
try {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(cookieName)) {
return cookie.getValue();
}
}
}
} catch (Exception e) {
ExceptionLogger.logDetailedError("CookieSessionUtils.getCookieByName", e);
log.error("Error on Cookie " + e.getMessage());
}
return "";
}
I then have this for my tests:
#WebMvcTest(value = CookieSessionUtils.class, includeFilters =
{#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ApiOriginFilter.class})})
class CookieSessionUtilsTest {
#Autowired
private CookieSessionUtils cookieSessionUtils;
#Mock
private HttpServletRequest request;
#BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testVerifyState() {
Cookie mockCookie1 = Mockito.mock(Cookie.class);
Cookie mockCookie2 = Mockito.mock(Cookie.class);
when(mockCookie1.getName()).thenReturn("stateToken");
when(mockCookie1.getValue()).thenReturn("stateToken");
when(mockCookie2.getName()).thenReturn("authn");
when(mockCookie2.getValue()).thenReturn("authn");
when(request.getCookies()).thenReturn(new Cookie[]{mockCookie1, mockCookie2});
when(cookieSessionUtils.getCookieByName(request, "stateToken")).thenReturn("stateToken");
when(cookieSessionUtils.getCookieByName(request, "authn")).thenReturn("authn");
assertTrue(cookieSessionUtils.verifyState(request));
}
However, it's always failing returning false falling into the return "" for the getCookieByName() method which seems to be triggered multiple times and having the value for getName() and getValue() overwritten by the other cookie, so it fails on (cookie.getName().equals(cookieName)). Not sure what I'm doing wrong.
Thank you.
No need to be so complicated. For mocking the servlet stuff, you can simply use the mock implementation provided by spring-test. It is more convenient to use than Mockito.
You can simply write your test case as :
#Test
public void testVerifyState() {
MockHttpServletRequest request = MockMvcRequestBuilders.get("/dummy")
.cookie(new MockCookie("stateToken", "stateToken"))
.cookie(new MockCookie("authn", "authn"))
.buildRequest(new MockServletContext());
assertTrue(cookieSessionUtils.verifyState(request));
}
Also , if the CookieSessionUtils that you are testing is just an utility class which does not have other spring bean dependencies, you can further simplify your test to just a plain JUnit test rather than a #WebMvcTest.
Based on #kenChan's answer, both of the below methods work. The issue was actually something very subtle and trivial. The value for the cookie was "authn" but its name is "Authn", so there wasn't a match between them due to capitalization.
private static final String STATE_TOKEN = "stateToken";
private static final String AUTHN = "Authn";
#Test
public void testVerifyState1() {
Cookie mockCookie1 = Mockito.mock(Cookie.class);
Cookie mockCookie2 = Mockito.mock(Cookie.class);
when(mockCookie1.getName()).thenReturn("stateToken");
when(mockCookie1.getValue()).thenReturn("stateToken");
when(mockCookie2.getName()).thenReturn("authn");
when(mockCookie2.getValue()).thenReturn("Authn");
when(request.getCookies()).thenReturn(new Cookie[]{mockCookie1, mockCookie2});
when(cookieSessionUtils.getCookieByName(request, "stateToken")).thenReturn("stateToken");
when(cookieSessionUtils.getCookieByName(request, "Authn")).thenReturn("Authn");
assertTrue(cookieSessionUtils.verifyState(request, ""));
}
#Test
public void testVerifyState() {
MockHttpServletRequest request = MockMvcRequestBuilders.get("/dummy")
.cookie(new MockCookie("stateToken", "stateToken"))
.cookie(new MockCookie("Authn", "authn"))
.buildRequest(new MockServletContext());
assertTrue(cookieSessionUtils.verifyState(request, ""));
}
I am trying a GetMapping After a PostMappng. Postmapping basically does an Update.
What I am doing is, if the condition satisfies then no need to update but simply redirect to another page. But seem it is not working, It seems a GetRequest is not Possible from a Postrequest but I have read articles and even seen solutions on Stack but dont know why dont they work for me. Any help or hint will be appreciated
#PostMapping(path = "/campaign/services/migrate")
public String migrateCampaigns(
#RequestParam(required = false, value = "campaignCount") int campaignCount,
#RequestParam(required = false, value = "client-email") String clientEmail,
#RequestParam(required = false, value = "campaign-id") List<String> campaignIds,
#RequestParam(required = false, value = "selectAllCampaigns") String selectAllCampaigns,
HttpServletRequest request,
HttpServletResponse httpResponse,
Model model) throws ServletException, IOException {
logger.info("Retrieving source and destination credential from cookies");
String url = null;
if(campaignCount >= 20) {
url = "redirect:/login/oauth/login-with-eloqua";
}
String adminsEmail = contactService.getEmail(siteNameForEmail);
String userEmail = cookieService.retrieveCookieValue(cookieDefinitionUserEmail);
String clientsEmailAddresses = adminsEmail + "," + userEmail;
migrateResponse = migrationService.migrate(campaignIds, request.getCookies(), clientsEmailAddresses);
}
//return migrateResponse;
return url;
}
#GetMapping(path = "/login/oauth/login-with-eloqua")
public String eloquaOauthLoginPage(HttpServletRequest request, Model model, HttpServletResponse response) {
return "login/oauth/login-with-eloqua";
}
There are many ways to get this approach, it's up on to you. I can see you have a class called HttpServletResponse, you can use a method that name isĀ of the class.
Example:
httpSrvltResponse.sendRedirect("/login/oauth/login-with-eloqua")`
I am trying to rewrite my login piece with Spring boot. Currently, my data is being posted fine and the backend is getting it, but my success function is not being fired. My backend is throwing no errors, but I am getting a 404 error on the browser.
Here is my post:
$.ajax({
type: "POST",
url: "login",
data: "&username=" + username.value + "&password=" + password.value
}).done(function(response) {
var resp = JSON.parse(response);
if (resp.loginResult === "false") {
//TODO
} else {
//TODO
}
});
Controller:
#Controller
#Scope("session")
public class LoginController {
#GetMapping("/login")
public String login() {
return "login";
}
#PostMapping("/login")
public String login(HttpServletRequest request) {
HttpSession session = request.getSession();
StringBuilder json = new StringBuilder();
String username = request.getParameter("username");
String password = request.getParameter("password");
if (userExists()) {
session.setAttribute("isLoggedIn", "true");
session.setAttribute("userID", username);
session.setAttribute("userType", "employee");
json.append("{");
json.append("\"loginResult\": \"true\",");
json.append("\"resultMessage\": \"Logged in\"");
json.append("}");
} else {
System.out.println("Username or password does not match.");
json.append("{");
json.append("\"loginResult\": \"false\",");
json.append("\"resultMessage\": \"Bad Login\"");
json.append("}");
}
return json.toString();
}
}
I am trying to just return a JSON string which can be parsed on the front end and do whatever needs to be done based off the resultMessage. Sorry if my code is ugly, I am still new to Spring and welcome any suggestions!
Here is the error in my console on the browser:
POST http://localhost:8080/BedrockWeb/login 404 ()
I am assuming I am not returning my JSON string properly.
If you use Spring, then use the conveniences that it provides. You can create following class:
public class LoginResult {
private boolean loginResult;
private String resultMessage;
public LoginResult() { }
public String getResultMessage() {
return resultMessage;
}
public boolean isLoginResult() {
return loginResult;
}
public void setLoginResult(boolean loginResult) {
this.loginResult = loginResult;
}
public void setResultMessage(String resultMessage) {
this.resultMessage = resultMessage;
}
}
Then you have to change your controller method to:
#PostMapping("/login")
#ResponseBody
public LoginResult login(HttpServletRequest request) {
HttpSession session = request.getSession();
String username = request.getParameter("username");
String password = request.getParameter("password");
LoginResult loginResult = new LoginResult();
if (userExists()) {
session.setAttribute("isLoggedIn", "true");
session.setAttribute("userID", username);
session.setAttribute("userType", "employee");
loginResult.setLoginResult(true);
loginResult.setResultMessage("Logged in");
} else {
System.out.println("Username or password does not match.");
loginResult.setLoginResult(false);
loginResult.setResultMessage("Bad Login");
}
return loginResult;
}
The #ResponseBody annotation tells a controller that the object returned is automatically serialized into JSON and passed back into the HttpResponse object. source
Yes, this is because you are not sending JSON response properly.
What you can do is create an object and set the values in that and then try to convert in JSON using
new JSONSerializer().transform(new DateTransformer("MM/dd/yyyy HH:mm:ss"), java.util.Date.class).exclude("*.class").serialize(object);
and send the response to AJAX like below:
return new ResponseEntity<String>(new JSONSerializer().transform(new DateTransformer("MM/dd/yyyy HH:mm:ss"), java.util.Date.class).exclude("*.class").serialize(object), HttpStatus.OK);
NOTE: If you want to do the same with Spring boot then the #ResponseBody annotation is enough. It will convert the object to JSON.
I have a method in a controller that shows a search page with some filters and a list of entities.
When I select one entity, I enter in edit-entity-page, the "save" button saves editing on db and go back to edit-entity-page.
Now: I should modify this behavior to go back to the list of entities.
This is the list method declaration, in my list controller:
#RequestMapping(value = "/customer/process/{filtro}/{processType}/filter.htm", method = RequestMethod.GET)
public String showHome(Map model, #PathVariable String filtro, #PathVariable String processType) {
and this is the forward I tried from detail controller:
return "forward:/customer/process/"+filtro+"/"+processType+"/filter.htm";
The forward method is called and list-entities-page displayed but I have some issue with the form on this page: every submit on it goes to the wrong controller (the detail controller instead of list controller)
(Spring 3.0.5)
Details asked by sgpalit :
This is what i called "detail controller" that manage the "save" process:
#Controller
public class ShipmentController {
#RequestMapping(method = RequestMethod.POST)
public String onSubmit(#ModelAttribute("shipmentForm") ShipmentForm shipmentForm, ModelMap model, HttpServletRequest request) {
return onSubmitNEW(model, shipmentForm, request);
}
public String onSubmitNEW(ModelMap model,ShipmentForm shipmentForm, HttpServletRequest request) {
--- save process ---
model.put("filtro",filtro);
model.put("processType", processType);
return "forward:/customer/process/"+filtro+"/"+processType+"/filterfrw.htm";
}
this is what i called "list controller":
#Controller
public class ProcessController {
#RequestMapping(value = "/customer/process/{filtro}/{processType}/filter.htm", method = RequestMethod.GET)
public String showHome(Map model, #PathVariable String filtro, #PathVariable String processType) {
CustomUserAuthentication ctoken = (CustomUserAuthentication) SecurityContextHolder.getContext().getAuthentication();
LoginCustomer loginCustomer = ctoken.getLoginCustomer();
LOG.debug("public String showHome()");
ResultFilterForm resultFilterForm = new ResultFilterForm();
List<ShipmentDetail> lDetails = new ArrayList<ShipmentDetail>();
List<ShipmentHeader> lShipments = new ArrayList<ShipmentHeader>();
SearchFiltersForm searchFiltersForm = new SearchFiltersForm();
searchFiltersForm = getSearchFiltersForm(searchFiltersForm);
searchFiltersForm.setNumPage(0);
searchFiltersForm.setProcessType(processType.toUpperCase());
searchFiltersForm.setFiltro(filtro.toUpperCase());
String strReturn = "";
List<String> chkPrint = new ArrayList<String>();
if (filtro.toUpperCase().equals("PIECES")) {
lDetails = shipmentDetailDAO.getDetails(null, processType, loginCustomer.getLcCustomer().getCwCustomer());
PagedListHolder pagedListHolder = new PagedListHolder(lDetails);
int page = 2;
pagedListHolder.setPage(searchFiltersForm.getNumPage());
pagedListHolder.setPageSize(pageSize);
model.put("lDetails", pagedListHolder.getPageList());
strReturn = "customer/process/listDetails";
if (lDetails.size() == 0) {
model.put("isEmpty", "msg_error");
}
if (loginCustomer.getLcFlagPrnLbl().equals("Y")) {
for (int i = 0; i < lDetails.size(); i++) {
chkPrint.add(lDetails.get(i).getSdCodPiece());
}
}
resultFilterForm.setCurrentPage(pagedListHolder.getPage() + 1);
resultFilterForm.setNumPage(pagedListHolder.getPageCount() - 1);
} else {
lShipments = shipmentHeaderDAO.getShipments(null, processType.toUpperCase(), loginCustomer.getLcCustomer().getCwCustomer());
PagedListHolder pagedListHolder = new PagedListHolder(lShipments);
int page = 2;
pagedListHolder.setPage(searchFiltersForm.getNumPage());
pagedListHolder.setPageSize(pageSize);
model.put("lShipments", pagedListHolder.getPageList());
strReturn = "customer/process/listShipments";
if (lShipments.size() == 0) {
model.put("isEmpty", "msg_error");
}
if (loginCustomer.getLcFlagPrnLbl().equals("Y")) {
for (int i = 0; i < lShipments.size(); i++) {
chkPrint.add(i + "");
}
}
resultFilterForm.setCurrentPage(pagedListHolder.getPage() + 1);
resultFilterForm.setNumPage(pagedListHolder.getPageCount() - 1);
}
resultFilterForm.setIsAddShipment(loginCustomer.getLcFlagCompMan());
if (loginCustomer.getLcFlagPrnLbl().equals("Y")) {
resultFilterForm.setChkPrint(chkPrint);
} else {
resultFilterForm.setChkPrint(null);
}
resultFilterForm.setSearchFiltersForm(searchFiltersForm);
resultFilterForm.setPageSize(pageSize);
model.put("resultFilterForm", resultFilterForm);
return strReturn;
}
}