Jasypt with spring 4.0 - spring-security

I am using spring security in my application through XML configuration.
This is my password encoder bean
<b:bean id="passwordEncoder"
class="org.springframework.security.crypto.password.StandardPasswordEncoder">
<b:constructor-arg value="ThisIsASecretSoChangeMe" />
</b:bean>
I want to replace it with Jasypt encryption.
How to integrate jasypt 1.9 with spring security 4.0.1.RELEASE?

If you are using spring-boot use this dependeny in pom
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>${jasypt-spring-boot-starter.version}</version>
</dependency>
and in your application.yml or application.properties file just put the Encrypted password enclosed with ENC() instead of plainpassword. Example
password:
encrypted:
password: ENC(nZ3U2bdJ05FHp1LYQbAVvDKkVs8Pi3Ke)
jasypt:
encryptor:
password: IfYouAreGoodAtSomethingNeverDoItForFree
Before this you need to generate this encrypted password from your plaintext password and the jasypt.encryptor.password(similar to salt, in this case IfYouAreGoodAtSomethingNeverDoItForFree). This can be done by something like this
java -cp ~/.m2/repository/org/jasypt/jasypt/1.9.2/jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="PasswordToBeEncrypted" password=<SecretKeyToEncryptDecrypt> algorithm=PBEWithMD5AndDES
or torough a java code. Here is a rough draft.
public class Md5Test {
public static void main(String[] args) throws NoSuchAlgorithmException {
String password = "plaintextpassword";
BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
textEncryptor.setPassword("IfYouAreGoodAtSomethingNeverDoItForFree ");
String myEncryptedText = textEncryptor.encrypt(password);
System.out.println(myEncryptedText);
BasicTextEncryptor textDecryptor = new BasicTextEncryptor();
textDecryptor.setPassword("IfYouAreGoodAtSomethingNeverDoItForFree ");
String plainText = textDecryptor.decrypt("QBPaH8HKE8JDaeIpJk66Kc8nGHtBfY+L");
System.out.println(plainText);
}
}

generated encrypted string from command does not give desired result as it can not encrypt special chards like "!".and gives error "event not found"
KAD#ashutosh MINGW64 ~/Desktop
$ java -cp
~/.m2/repository/org/jasypt/jasypt/1.9.3/jasypt-1.9.3.jar
org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI
input="Test!email30#password" password="some_salt"
algorithm=PBEWithMD5AndDES
bash: !email30#password: event not found
Here is an example using org.jasypt.util.text.AES256TextEncryptor
This is a utility class for easily performing high-strength encryption of texts.
This class internally holds a StandardPBEStringEncryptor configured this way:
Algorithm: PBEWithHMACSHA512AndAES_256.
Key obtention iterations: 1000.
The required steps to use it are:
Create an instance (using new).
Set a password (using setPassword(String) or setPasswordCharArray(char[])).
Perform the desired encrypt(String) or decrypt(String) operations.
pom.xml:
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
You can use jasypt latest 2.1.2(with boot 2.1.1) or jasypt-1.9.3.jar.
Java Code:
import org.jasypt.util.text.AES256TextEncryptor;
import java.security.NoSuchAlgorithmException;
public class JasyptPasswordEcryptor {
public static void main(String[] args) throws NoSuchAlgorithmException {
String password = "Test!email30#password";
AES256TextEncryptor encryptor = new AES256TextEncryptor();
encryptor.setPassword("some_salt");
String myEncryptedText = encryptor.encrypt(password);
System.out.println("Encrypted: "+myEncryptedText);
String plainText = encryptor.decrypt(myEncryptedText);
System.out.println("Decrypted: "+plainText);
}
}
Output:
Encrypted:
fureWQHrflMinY+KBOcNeJyYmQv+7Ung/IclGz3iSBYKqTNdgslADg+TMcfFI/unaqZ/P3kDGPco2jZ4vIhrFw==
Decrypted: Test!email30#password
Spring Boot Integration:
You can use #EnableEncryptableProperties in your any configuration class or #SpringBootApplication. See example:
import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#EnableEncryptableProperties
#SpringBootApplication
#ComponentScan(basePackages = {"com.company"})
#EntityScan(basePackages = {"com.company.persistence.entities"})
#EnableJpaRepositories(value = {"com.company.persistence.repository"})
#EnableTransactionManagement
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
And in any properties/yml file:
email:
password:
# DO-NOT-USE/REMOVE THIS
plain: 'Test!email30#password'
# use this encrypted one
encrypted: ENC(fureWQHrflMinY+KBOcNeJyYmQv+7Ung/IclGz3iSBYKqTNdgslADg+TMcfFI/unaqZ/P3kDGPco2jZ4vIhrFw==)
jasypt:
encryptor:
password: some_salt

Related

How to enable SecurityDefinitions in "../v2/api-docs" json generated file

I want to use swagger client generator and feed the json generated by "../v2/api-docs" from the jHipster application. The problem is that without the security definitions the generated code will not work. The JWT token is not added to the API requests, the code is generated without authentication. The http://petstore.swagger.io/v2/swagger.json example has security and securityDefinitions. Where to modify/configure the jhipster application so that the security and security definitions are generated in the json file? {I manually added the security and security definitions to the json file and after that the generated code works and JWT is enabled in the jHipster application, but I don't want to edit the file each time the API changes... } The "securityDefinitions" and "security":[{"petstore_auth":["write:pets","read:pets"]}] sections are completely missing from the generated json file from the jHipster application, even if JWT is enabled and needed to make API requests.
Update 28-09-2020:
Since the update to SpringFox 3, classes are now called
SpringfoxCustomizer
JHipsteSpringfoxCustomizer
Better late than never.
JHipster applications depend on the JHipster Framework, which is in charge of the springfox's Docket configuration.
JHipster Framework's SwaggerAutoConfiguration customizes the springfox Docket with every SwaggerCustomizer bean registered in the application. JHipster registers it's own swagger customizer for the default docket configuration.
This said, you need to add your own docket customizer ir order to include the desired security definitions and any other additional configuration to the springfox's docket. In order to do this you need to:
Create the swagger pacakage inside the already existing config package. Inside it, create a CustomSwaggerConfig class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class CustomSwaggerConfig {
public CustomSwaggerConfig() {
}
#Bean
public ApplicationSwaggerCustomizer applicationSwaggerCustomizer() {
return new ApplicationSwaggerCustomizer();
}
}
And create the ApplicationSwaggerCustomizer class:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.jhipster.config.apidoc.customizer.SwaggerCustomizer;
import springfox.documentation.spring.web.plugins.Docket;
public class ApplicationSwaggerCustomizer implements SwaggerCustomizer {
private final Logger log = LoggerFactory.getLogger(ApplicationSwaggerCustomizer.class);
public ApplicationSwaggerCustomizer() {
}
#Override
public void customize(Docket docket) {
log.debug("Customizing springfox docket...");
// TODO Here you can add all the configurations to the docket
}
}
Now you can add any additional docket configuration.
You can clone default implementation with:
package <YOUR_PACKAGE>;
import static io.github.jhipster.config.JHipsterConstants.SPRING_PROFILE_SWAGGER;
import static springfox.documentation.builders.PathSelectors.regex;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import io.github.jhipster.config.JHipsterProperties;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.ApiKeyVehicle;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* Springfox Swagger configuration.
* <p>
* Warning! When having a lot of REST endpoints, Springfox can become a performance issue.
* In that case, you can use the "no-swagger" Spring profile, so that this bean is ignored.
*/
#Configuration
#Profile(SPRING_PROFILE_SWAGGER)
#EnableSwagger2
public class SwaggerConfiguration {
static final String STARTING_MESSAGE = "Starting Swagger with JWT";
static final String STARTED_MESSAGE = "Started Swagger with JWT in {} ms";
static final String MANAGEMENT_TITLE_SUFFIX = "Management API";
static final String MANAGEMENT_GROUP_NAME = "management";
static final String MANAGEMENT_DESCRIPTION = "Management endpoints documentation";
public static final String AUTHORIZATION_HEADER = "Authorization";
private final Logger log = LoggerFactory.getLogger(SwaggerConfiguration.class);
private final JHipsterProperties.Swagger properties;
public SwaggerConfiguration(JHipsterProperties jHipsterProperties) {
this.properties = jHipsterProperties.getSwagger();
}
/**
* Springfox configuration for the API Swagger with JWT docs.
*
* #return the Swagger Springfox configuration
*/
#Bean
public Docket swaggerSpringfoxApiDocket() {
log.debug(STARTING_MESSAGE);
StopWatch watch = new StopWatch();
watch.start();
Docket docket = createDocket();
Contact contact = new Contact(
properties.getContactName(),
properties.getContactUrl(),
properties.getContactEmail()
);
ApiInfo apiInfo = new ApiInfo(
properties.getTitle(),
properties.getDescription(),
properties.getVersion(),
properties.getTermsOfServiceUrl(),
contact,
properties.getLicense(),
properties.getLicenseUrl(),
new ArrayList<>()
);
docket.host(properties.getHost())
.protocols(new HashSet<>(Arrays.asList(properties.getProtocols())))
.securitySchemes(Arrays.asList((apiKey())))
.securityContexts(Arrays.asList(
SecurityContext.builder()
.securityReferences(
Arrays.asList(SecurityReference.builder()
.reference("JWT")
.scopes(new AuthorizationScope[0])
.build()
)
)
.build())
)
.apiInfo(apiInfo)
.useDefaultResponseMessages(properties.isUseDefaultResponseMessages())
.forCodeGeneration(true)
.directModelSubstitute(ByteBuffer.class, String.class)
.genericModelSubstitutes(ResponseEntity.class)
.ignoredParameterTypes(Pageable.class)
.select()
.paths(regex(properties.getDefaultIncludePattern()))
.build();
watch.stop();
log.debug(STARTED_MESSAGE, watch.getTotalTimeMillis());
return docket;
}
/**
* Springfox configuration for the management endpoints (actuator) Swagger docs.
*
* #param appName the application name
* #param managementContextPath the path to access management endpoints
* #return the Swagger Springfox configuration
*/
#Bean
#ConditionalOnMissingBean(name = "swaggerSpringfoxManagementDocket")
public Docket swaggerSpringfoxManagementDocket(#Value("${spring.application.name:application}") String appName,
#Value("${management.endpoints.web.base-path}") String managementContextPath) {
ApiInfo apiInfo = new ApiInfo(
StringUtils.capitalize(appName) + " " + MANAGEMENT_TITLE_SUFFIX,
MANAGEMENT_DESCRIPTION,
properties.getVersion(),
"",
ApiInfo.DEFAULT_CONTACT,
"",
"",
new ArrayList<>()
);
return createDocket()
.apiInfo(apiInfo)
.useDefaultResponseMessages(properties.isUseDefaultResponseMessages())
.groupName(MANAGEMENT_GROUP_NAME)
.host(properties.getHost())
.protocols(new HashSet<>(Arrays.asList(properties.getProtocols())))
.securitySchemes(Arrays.asList((apiKey())))
.securityContexts(Arrays.asList(
SecurityContext.builder()
.securityReferences(
Arrays.asList(SecurityReference.builder()
.reference("JWT")
.scopes(new AuthorizationScope[0])
.build()
)
)
.build())
)
.forCodeGeneration(true)
.directModelSubstitute(ByteBuffer.class, String.class)
.genericModelSubstitutes(ResponseEntity.class)
.ignoredParameterTypes(Pageable.class)
.select()
.paths(regex(managementContextPath + ".*"))
.build();
}
protected Docket createDocket() {
return new Docket(DocumentationType.SWAGGER_2);
}
private ApiKey apiKey() {
return new ApiKey("JWT", AUTHORIZATION_HEADER, ApiKeyVehicle.HEADER.getValue());
}
} // END
At first i got a similar problem like yours and i searched to find your post.
But my project uses .net core,and from the url below i found a solution.
Hope it could help you if you haven't got your problem fixed.
https://github.com/domaindrivendev/Swashbuckle.AspNetCore#add-security-definitions-and-requirements

UserTypeResolver must not be null

I have been attempting to test out an insert of a Cassandra UDT, and i keep running into the following error:
Exception in thread "main" java.lang.IllegalArgumentException: UserTypeResolver must not be null
After just trying to figure my own way through it, i attempted to exactly replicate the approach outlined in the following:
User Defined Type with spring-data-cassandra
However, i still get the same error.
I am able to insert to the target DB when i remove the UDT and just insert the simple types, so I know that I am connecting appropriately. My config is as follows:
#Configuration
#PropertySource(value = { "classpath:cassandra.properties" })
//#EnableCassandraRepositories(basePackages = { "org.spring.cassandra.example.repo" })
public class CassandraConfig {
private static final Logger LOG = LoggerFactory.getLogger(CassandraConfig.class);
#Autowired
private Environment env;
#Bean
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints(env.getProperty("cassandra.contactpoints"));
cluster.setPort(Integer.parseInt(env.getProperty("cassandra.port")));
return cluster;
}
#Bean
public CassandraMappingContext mappingContext() {
BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
mappingContext.setUserTypeResolver(new SimpleUserTypeResolver(cluster().getObject(), "campaign_management"));
return mappingContext;
}
#Bean
public CassandraConverter converter() {
return new MappingCassandraConverter(mappingContext());
}
#Bean
public CassandraSessionFactoryBean session() throws Exception {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
session.setCluster(cluster().getObject());
session.setKeyspaceName(env.getProperty("cassandra.keyspace"));
session.setConverter(converter());
session.setSchemaAction(SchemaAction.NONE);
return session;
}
#Bean
public CassandraOperations cassandraTemplate() throws Exception {
return new CassandraTemplate(session().getObject());
}
}
My Address and Employee classes are exactly as shown in the SO question i reference above, and my Main is simply:
public class MainClass {
public static void main(String[] args) {
ApplicationContext service = new AnnotationConfigApplicationContext(CassandraConfig.class);
Employee employee = new Employee();
employee.setEmployee_id(UUID.randomUUID());
employee.setEmployee_name("Todd");
Address address = new Address();
address.setAddress_type("Home");
address.setId("ToddId");
employee.setAddress(address);
CassandraOperations operations = service.getBean("cassandraTemplate", CassandraOperations.class);
operations.insert(employee);
System.out.println("Done");
}
}
I am using:
datastax.cassandra.driver.version=3.1.3
spring.data.cassandra.version=1.5.1
spring.data.commons.version=1.13.1
spring.cql.version=1.5.1
The version referenced in the previous SO question is 1.5.0, though spring.io lists 1.5.1 as current, so I am using that, and no 1.5.0 is shown available.
Any help would be appreciated, as this is driving me somewhat nuts.
You typically get this error when you miss a UserTypeResolver under your cassandra Mapping, itself used by the cassandra Converter, itself used by the Spring Data Cassandra Template
For the details:
Assuming you have a basic Spring MVC Controller up and running elsewhere...
UserDefinedTypes in Cassandra being most interesting within SETs and MAPs, the example below is of such kind.
Example Spring Bean configuration with all defaults (Spring XML application context extract):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cassandra="http://www.springframework.org/schema/data/cassandra"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
...
<!-- ===== CASSANDRA ===== -->
<!-- Loads the properties into the Spring Context and uses them to fill in placeholders in bean definitions below -->
<context:property-placeholder location="/WEB-INF/spring/cassandra.properties" />
<!-- REQUIRED: The Cassandra Cluster -->
<cassandra:cluster contact-points="${cassandra.contactpoints}"
port="${cassandra.port}" username="cassandra" password="cassandra"
auth-info-provider-ref="authProvider" />
<!-- REQUIRED: The Cassandra Session, built from the Cluster, and attaching to a keyspace -->
<cassandra:session keyspace-name="${cassandra.keyspace}" />
<!-- REQUIRED: The Default Cassandra Mapping Context used by CassandraConverter
DO include a userTypeResolver for UDT support -->
<cassandra:mapping entity-base-packages="fr.woobe.model">
<cassandra:user-type-resolver keyspace-name="${cassandra.keyspace}" />
</cassandra:mapping>
<!-- REQUIRED: The Default Cassandra Converter used by CassandraTemplate -->
<cassandra:converter />
<bean id="authProvider" class="com.datastax.driver.core.PlainTextAuthProvider">
<constructor-arg index="0" value="myCassandraUser" />
<constructor-arg index="1" value="somePassword" />
</bean>
<!-- REQUIRED: The Cassandra Template is the building block of all Spring Data Cassandra -->
<cassandra:template id="cassandraTemplate" />
...
and then in java, typically within your Spring MVC controller:
import org.springframework.data.cassandra.core.CassandraOperations;
...
// acquire DB template
CassandraOperations cOps = this.beanFactory.getBean("cassandraTemplate", CassandraOperations.class);
// for instance: load everything
List<MyData> rows = cOps.select("SELECT * FROM mydatatable", MyData.class);
// assuming an entry with index i exists...
Set<Pair> mySetOfPairs = rows.get(i).pairSet;
if (mySetOfPairs!=null)
for (Pair p : mySetOfPairs) {
... handle p.first and p.second ...
...
with this kind of entity mappings:
package example.model;
import java.util.Set;
import org.springframework.data.cassandra.core.mapping.CassandraType;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;
import com.datastax.driver.core.DataType.Name;
#Table public class MyData {
#PrimaryKey
public String myKey;
// some other basic fields...
public String moreStuff;
// a SET of user defined 'pair type'
#CassandraType(type = Name.SET, userTypeName = "pairType")
public Set<Pair> pairSet;
// your constructors and other methods here...
}
and a user defined entity like:
package example.model;
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
#UserDefinedType("pairType")
public class Pair {
public String first;
public String second;
public Pair() {
}
public Pair(String f, String s) {
this.first= f;
this.second= s;
}
}
all based on a Cassandra table created as:
CREATE TYPE pairType (first text, second text);
CREATE TABLE MyData (
myKey text,
moreStuff text,
pairSet set<frozen<pairType>>,
PRIMARY KEY (myKey)
) ;
INSERT INTO MyData (myKey, moreStuff, pairSet)
VALUES ('hello', 'world', {
{ first:'one', second:'two' },
{ first:'out', second:'there' } }
) ;
In term of Maven artifacts or libraries, spring-webmvc is indeed required if you run within a Web MVC Spring Controller, and then spring-context-support, and spring-data-cassandra. The DataStax cassandra driver comes along as a dependency.

Simulate Anonymous Authentication in Spring MVC Unit Test

I am trying to write a unit test for a guest user account. The code under test checks guest by calling this method, which in Unit Test returns null for the guest account.
/**
* Determines if the user is a guest account.
*
* #return True if the account is guest account, false otherwise.
*/
public boolean isGuest() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
if (auth instanceof AnonymousAuthenticationToken) {
return true;
} else {
return false;
}
} else {
return false;
}
}
In the server Tomcat container, the anonymous user is okay it returns an instance of AnonymousAuthenticationToken. Because the container environment & unit test environment both share the same security configuration class, assume the security config is probably correct.
The test code below also works with the MockUser so I also think the security test configuration is probably okay:
#Test
#WithMockUser(username="Test.Customer.1#mailinator.com", roles = {"ADMIN"})
public void testCheckoutPage() throws Exception{
logger.entry();
String targetView = OrderViews.convertViewReference(getPageDirectory(), OrderViews.CHECKOUT_LOGIN_PAGE, false);
String targetUrl = "/checkout";
Order order = OrderBuilder.buildSampleGuestOrder(OrderStatus.NEW, 5);
prepareMocks(order);
Map<String, Object> sessionAttrs = new HashMap<>();
sessionAttrs.put(OrderConstants.OPEN_ORDER_ID_ATTRIBUTE, order.getId());
this.mockMvc.perform(get(targetUrl).sessionAttrs(sessionAttrs))
.andExpect(status().isOk())
.andExpect(view().name(targetView))
.andExpect(model().attribute("order", order))
.andExpect(model().attributeExists("loginForm"));
this.mockMvc.perform(MockMvcRequestBuilders.post(targetUrl))
.andExpect(status().isMethodNotAllowed());
logger.exit();
}
Does anyone have an idea how to simulate the anonymous authentication token in Unit Test?
In Spring Security 4.1 (not yet GA) we are introducing support for #WithAnonymousUser.
The #WithAnonymousUser support is built using #WithSecurityContext. This means you could easily add the support to your codebase in 4.0.x until 4.1.x is released. To get it to work you would need to copy the following classes to your test source folders:
package org.springframework.security.test.context.support;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
#WithSecurityContext(factory = WithAnonymousUserSecurityContextFactory.class)
public #interface WithAnonymousUser {}
package org.springframework.security.test.context.support;
import java.util.List;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
final class WithAnonymousUserSecurityContextFactory implements
WithSecurityContextFactory<WithAnonymousUser> {
public SecurityContext createSecurityContext(WithAnonymousUser withUser) {
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS");
Authentication authentication = new AnonymousAuthenticationToken("key", "anonymous", authorities);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
Then you can use the following to run as an anonymous user:
#Test
#WithAnonymousUser
public void testAnonymous() throws Exception {
// ...
}
NOTE: It is important to note that just as you needed to do for #WithMockUser you need to ensure you setup MockMvc with apply(springSecurity()) as outlined in the reference.
Set the authentication before running test
#Before
public void setupAuthentication(){
SecurityContextHolder.getContext().setAuthentication(new AnonymousAuthenticationToken("GUEST","USERNAME", AuthorityUtils
.createAuthorityList("ROLE_ONE", "ROLE_TWO")));
}

Why is the configured servlet path correctly used by the REST controllers, but ignored in Spring Security features?

I'm new to Spring and I try to create a secured rest application using Spring Boot and Spring Security. I'm searching for weeks for a solution now...
I'm using Spring Boots embedded web container (Tomcat) and the spring-boot-starter-parent 1.2.6.RELEASE in my pom.
My endpoints:
/login (to authenticate)
/application/{id} (some service which I want to secure)
I configured my servlet path in my application.properties like this:
server.servletPath: /embedded
so I expect my services e.g. on //localhost/embedded/login
Ok so now the problem: If I run the application without security everything is fine, I can call http//localhost/embedded/application and get an answer.
If I now add my security configuration like this:
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebMvcSecurity
#EnableScheduling
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
#Value("${server.servletPath}")
private String servletPath;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/hello/**", "/login").permitAll()
.antMatchers("/application/**").authenticated().and()
.addFilterBefore(new TokenAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class);
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.httpBasic().disable();
}
}
when running the application //localhost/application/{id} is secured instead of
//localhost/embedded/application/{id} as I would have expected.
For some reason the servlet path is ignored there. I tought "ok so I just add the servlet path manually" and make it look like this:
...antMatchers(servletPath+"/application/**").authenticated()...
This works in my application. However I also use MockMvc to test my services and for some reason there the servlet path is correctly added to the matchers. So if I start the tests the security filters are mapped to //localhost/embedded/embedded/application/{id} while the controllers themselves still are mapped to //localhost/embedded/application/{id} which is very annoying...
I took a look at here http://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/ and thought I could fix the issue by using AbstractSecurityWebApplicationInitializer instead of SpringBootServletInitializer but it changed nothing.
This is my application class by the way:
com.sebn.gsd.springservertemplate.service.security.WebSecurityConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
System.out.println("Run from main");
SpringApplication.run(applicationClass, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass, WebSecurityConfig.class);
}
private static Class<Application> applicationClass = Application.class;
}
The application.properties doesn't contain any more interesting information I think. To be complete this is my MockMvc testing class:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sebn.gsd.springservertemplate.service.api.LoginData;
import com.sebn.gsd.springservertemplate.service.security.Session_model;
import com.sebn.gsd.springservertemplate.service.security.WebSecurityConfig;
import java.util.Arrays;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.notNullValue;
import org.junit.Assert;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.web.servlet.ResultActions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
import org.springframework.web.context.WebApplicationContext;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {Application.class, WebSecurityConfig.class })
#WebAppConfiguration
#ActiveProfiles(profiles = "development")
public class SecurityTests {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
private HttpMessageConverter mappingJackson2HttpMessageConverter;
private ObjectMapper o = new ObjectMapper();
#Autowired
private FilterChainProxy filterChainProxy;
#Value("${server.servletPath}")
private String servletPath;
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext).addFilter(filterChainProxy).build();
}
#Test
public void testLoginSecurity() throws Exception {
int applicationId = 1;
// Try to access secured api
ResultActions actions = mockMvc.perform(get("/application/" + applicationId))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isForbidden());
//login
String username = "user";
LoginData loginData = new LoginData();
loginData.setPasswordBase64("23j4235jk26=");
loginData.setUsername(username);
actions = mockMvc.perform(post("/login").content(o.writeValueAsString(loginData)).contentType(MediaType.APPLICATION_JSON_VALUE))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.login", Matchers.equalTo(username)))
.andExpect(jsonPath("$.token", notNullValue()))
.andExpect(jsonPath("$.expirationDate", notNullValue()));
Session_model session = getResponseContentAsJavaObject(actions.andReturn().getResponse(), Session_model.class);
Assert.assertNotNull(session);
// Try to access secured api again
actions = mockMvc.perform(get("/application/" + applicationId).header("X-AUTH-TOKEN", session.getToken()))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk());
}
private <T> T getResponseContentAsJavaObject(MockHttpServletResponse response, Class<T> returnType) throws Exception{
return o.readValue(response.getContentAsString(), returnType);
}
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter(
hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get();
Assert.assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
}
Maybe I misunderstood something. I hope you can tell me.
Summary
In short you need to map Spring Security to use include the servlet path. Additionally, you need to include the servlet path in your MockMvc requests. To do so you can perform something like:
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext)
// ADD LINE BELOW!!!
.defaultRequest(get("/").servletPath(servletPath))
.addFilter(filterChainProxy)
.build();
}
Detailed Response
Spring Security Matches Based on Context Root
Spring Security's matchers are relative to the application's context root. It is not relative to the servlet path. This is deliberate because it should protect all the servlets (not just Spring MVC). If it were relative to the servlet, consider the following:
servlet1-path/abc -> Only users with role ROLE_ADMIN can access
servlet2-path/abc -> Only users with role ROLE_USER can access
How would you differentiate between these two mappings if Spring Security were relative to the servlet path?
Working in Mock MVC
The reason Spring Security is working in MockMvc is because when you are using MockMvc the servlet path is no longer considered. Your requests are being sent to Spring Security and Spring MVC as though the servlet path is "". To fix this you need to include the servlet path in the request.
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext)
// ADD LINE BELOW!!!
.defaultRequest(get("/").servletPath(servletPath))
.addFilter(filterChainProxy)
.build();
}

Configure Spring Security SAML to use SHA-256 as secure hash algorithm

I'm working on an integration between Spring SAML and Microsoft ADFS 3.0. Even it is already stated in the documentation of Spring SAML as:
Open the provider by double-clicking it, select tab Advanced and change
"Secure hash algorithm" to SHA-1
that I understand that Spring SAML supports currently only SHA-1 as hash algorithm, but my requirement is using SHA-256. If I try configure only in ADFS for SHA-256, it doesn't work. I suppose that I have to do something with Spring SAML. Do you have any idea how to do so?
You should configured the Spring security configuration to use SHA-256 signature algorithm.
You could either override the SAMLBootstrap or configure a initializing bean like this:
Spring configuration:
<bean id="samlProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:saml.properties" />
</bean>
<bean class="your.package.SAMLConfigurationBean">
<property name="signatureAlgorithm" value="${saml.signatureAlgorithm:SHA1}" />
</bean>
properties file (saml.properties):
saml.signatureAlgorithm=SHA256
Initializing bean:
package your.package;
import org.opensaml.Configuration;
import org.opensaml.xml.security.BasicSecurityConfiguration;
import org.opensaml.xml.signature.SignatureConstants;
import org.springframework.beans.factory.InitializingBean;
public class SAMLConfigurationBean implements InitializingBean {
private String signatureAlgorithm ;
private String digestAlgorithm;
public void setSignatureAlgorithm(String algorithm) {
switch (algorithm) {
case "SHA256" :
signatureAlgorithm = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256;
digestAlgorithm = SignatureConstants.ALGO_ID_DIGEST_SHA256;
break;
case "SHA512" :
signatureAlgorithm = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512;
digestAlgorithm = SignatureConstants.ALGO_ID_DIGEST_SHA512;
break;
default:
signatureAlgorithm = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1;
digestAlgorithm = SignatureConstants.ALGO_ID_DIGEST_SHA1;
}
}
#Override
public void afterPropertiesSet() throws Exception {
BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
config.registerSignatureAlgorithmURI("RSA", signatureAlgorithm);
config.setSignatureReferenceDigestMethod(digestAlgorithm);
}
}
You could also skip the configurable part and just settle for this:
Initializing bean:
package your.package;
import org.opensaml.Configuration;
import org.opensaml.xml.security.BasicSecurityConfiguration;
import org.opensaml.xml.signature.SignatureConstants;
import org.springframework.beans.factory.InitializingBean;
public class SAMLConfigurationBean implements InitializingBean {
#Override
public void afterPropertiesSet() throws Exception {
BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
}
}
I recommend to refer to this GitHub example project: https://github.com/choonchernlim/spring-security-adfs-saml2
which provides ADFS dedicated configuration information and details how to enable SHA-256 signature.

Resources