Showing posts with label Security. Show all posts
Showing posts with label Security. Show all posts

Security


Certificate

CSR(Certificate Signing Request)

Create a keypair

keytool -genkeypair -keystore keystore.p12 -storetype PKCS12 
-alias ${THE_ALIAS} -keyalg RSA -keysize 2048 
-dname "CN=${THE_CN}" -storepass ${THE_PASSWORD}
 
#### Create a Certificate Signing Request
keytool -certreq -keystore keystore.p12 -storetype PKCS12 
-alias ${THE_ALIAS} -sigalg SHA256withRSA -storepass ${THE_PASSWORD}
 
#### Install the downloaded certificate.chain.pem to the keystore
keytool -import -keystore keystore.p12 -alias ${THE_ALIAS} 
-trustcacerts -file the.chain.pem -storepass ${THE_PASSWORD}
 
##### Extract private key
openssl pkcs12 -nocerts -in keystore.p12 -out the_private.key -nodes
 
##### Convert the pem to PKCS12 Keystore
openssl pkcs12 -export -in the.chain.pem -out keystore.p12 
-inkey the_private.key -name ${THE_ALIAS} -noiter -nomaciter

Concepts

alias
  • unique string to identify the key entry
trustStore vs keyStore
  • Keystore is used by a server to store private keys, and truststore is used by third party client to store public keys provided by server to access.

Troubleshooting Spring Security Multiple JSESSIONID Cookie Issue


The problem
There is one weird issue in our application that sometimes after login, it redirects to index.html but it failed to load.

The problem only happens occasionally.

Troubleshooting Process
Use chrome devtool to check requests in network panel, some protected api works fine, but some fails and sends redirect to login page due to 302 Found. 
-- This is weird. Usually it should fail/succeed for all. This leads me to check the difference between succeeded and failed requests.

Then I check the cookie settings in chrome://settings/cookies
I saw there are 2 JSESSIONID, one in path /, one in path /v1/data

Reproduce the issue
Next I tried to find a way to reliably and conveniently reproduce the issue, then found out that:
The problem happens if I first visits apis like v1/data/products. No problem if I first visits /login or /index.html.

Then I check more detail at the request and response in network panel.
Found that when first access v1/data/products, the response is 302, and redirect to login page, the cookie is:
Set-Cookie:JSESSIONID=8C70B083FB7FEAD57F6B6ADF9817E48C; Path=http://localhost/myapp_jbuild_number/; Secure; HttpOnly

Spring created one session for this anonymous user so later it can redirect to original page after login. The session 8C70B083FB7FEAD57F6B6ADF9817E48C is anonymous user.

After login, for api that succeeded, such as v1/xxx, it uses right JSESSIONID.

But for apis failed like v1/data/xx, there are 2 JSESSIONID, in the request headers.
Cookie:JSESSIONID=8C70B083FB7FEAD57F6B6ADF9817E48C; JSESSIONID=1BE7FFCA12C3882AE6F651DBA3759964

As the anonymous user session 8C70B083FB7FEAD57F6B6ADF9817E48C is for path v1/data
the logined user session 1BE7FFCA12C3882AE6F651DBA3759964 is for /, so the server uses the anonymous user session 8C70B083FB7FEAD57F6B6ADF9817E48C.

This is why the request to v1/data/xx failes and returns 302 and redirect to login page.

2016-10-13 00:09:56:0934 INFO  2268116 [ajp-nio-8009-exec-2] Spring Security Debugger    -
New HTTP session created: 8C70B083FB7FEAD57F6B6ADF9817E48C
Request received for GET '/v1/data/xxxx':
cookie: JSESSIONID=8C70B083FB7FEAD57F6B6ADF9817E48C; JSESSIONID=1BE7FFCA12C3882AE6F651DBA3759964;
2016-10-13 00:10:10:0330 DEBUG 2281512 [ajp-nio-8009-exec-4] o.s.s.w.a.AnonymousAuthenticationFilter    - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@6faa6108: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffff10d0: RemoteIpAddress: 173.230.196.25; SessionId: 8C70B083FB7FEAD57F6B6ADF9817E48C; Granted Authorities: ROLE_ANONYMOUS'

But Why created one cookie JSESSIONID at path v1/data?
At first, I thought it's because of spring.
But after several hours, I check the request and response again, and wonder why the path is like: Path=http://localhost/myapp_jbuild_number/

This leads me to check apache httpd configuration, and found out the root cause:
ProxyPassReverseCookiePath / http://localhost/myapp-version_jbuild_number/

This is not right and cause wrong path in response header: Set-Cookie: Path=http://localhost/myapp_jbuild_number/

After removed it, it works.
the path in response header is Set-Cookie: ... Path=/;

Misc: WebSecurityConfigurerAdapter
To enable the DebugFilter in spring security, configure WebSecurity in WebSecurityConfigurerAdapter subclass. It will log information (such as session creation) to help the user understand how requests are being handled by Spring Security - But never do this in production.
public void configure(WebSecurity web) throws Exception {
    web.debug(true);
}


ProxyPassReverseCookiePath internal-path public-path
- Rewrite the path string in Set-Cookie headers. If the beginning of the cookie path matches internal-path, the cookie path will be replaced with public-path.

Happy Troubleshooting.

Spring Security - Build Multi-Tenant Application


The problem
We are evolving our application from single-purpose to multi-tenant application.

The solution
We use LDAP to authenticate user and define different LDAP Group for different roles in different environment for different sub-application.

In login page, user selects what sub-applications to login. The application will call LDAP to do authentication, which will return what what groups user belongs to. Then the application will check the group-mapping to decide whether user can access this application and what roles user should have.

We also store the sub-application name in the session, so it can be used later.


We store supported Applications - the application name and the mapping of application's ldap groups in database.

Check Spring Security: Integrate In-Memory Authentication for Test Automation for why we add test users in dev lines and how to do it.


Talk is cheap. Show me the code.
@Component
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
 @Autowired
 private Environment environment;
 @Autowired
 private IConfigService configService;

 @Autowired
 private ApplicationProfile applicationProfile;
  // these test users are cross all applications in dev lines
 private final Set$lt;String$gt; testUsers = new HashSet$lt;$gt;();

 @PostConstruct
 public void postConstruct() {
  if (applicationProfile.isDev()) {
   addTestUser("spring.security.test.user.adminOnly.name");
   addTestUser("spring.security.test.user.provisionerOnly.name");
   addTestUser("spring.security.test.user.adminProvisioner.name");
  }
 }

 protected void addTestUser(final String testUserProperty) {
  final String testUser = environment.getProperty(testUserProperty);
  if (StringUtils.isNotBlank(testUser)) {
   testUsers.add(testUser);
  }
 }

 @Autowired
 @Override
 public void setAuthenticationManager(final AuthenticationManager authenticationManager) {
  super.setAuthenticationManager(authenticationManager);
 }

 @Override
 public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response)
   throws AuthenticationException {
  final String applicationName = request.getParameter(Util.APPLICATION_NAME);

  if (StringUtils.isEmpty(applicationName)) {
   throw new AuthenticationServiceException(
     MessageFormat.format("Not supported application: {0}", applicationName));
  }

  final Map$lt;String, SupportedAppSecurityConfig$gt; supportedApps = configService.getMySimpleConfig()
    .extractSupportedApplications();
  if (!supportedApps.containsKey(applicationName)) {
   throw new AuthenticationServiceException(
     MessageFormat.format("Not supported application: {0}", applicationName));
  }

  final Authentication auth = super.attemptAuthentication(request, response);

  if (auth.isAuthenticated()) {
   request.getSession(true).setAttribute(Util.APPLICATION_NAME, applicationName);
   if (testUsers.contains(auth.getName())) {
    return auth;
   }
   return checkAuthorizationAndMappingGroup(supportedApps, applicationName, auth);
  }
  return auth;
 }

 protected Authentication checkAuthorizationAndMappingGroup(
   final Map$lt;String, SupportedAppSecurityConfig$gt; supportedApps, final String applicationName,
   final Authentication auth) {
  // mapping group
  final SupportedAppSecurityConfig application = supportedApps.get(applicationName);

  final List$lt;GrantedAuthority$gt; newAuthorities = new ArrayList$lt;$gt;();

  boolean isAdmin = false, isProvisioner = false;
  for (final GrantedAuthority authority : auth.getAuthorities()) {
   if (authority.getAuthority().equals(application.getAdminLadpGroup())) {
    isAdmin = true;
   }
   if (authority.getAuthority().equals(application.getProvisionLdapGroup())) {
    isProvisioner = true;
   }
  }

  if (!isAdmin && !isProvisioner) {
   throw new AuthenticationServiceException(MessageFormat
     .format("User {0} does not have expected authority, having: {1}", auth.getName(), newAuthorities));
  }

  if (isAdmin) {
   newAuthorities.add(new SimpleGrantedAuthority(Util.ADMIN_GROUP));
  }
  if (isProvisioner) {
   newAuthorities.add(new SimpleGrantedAuthority(Util.PROVISION_GROUP));
  }

  final Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(),
    auth.getCredentials(), newAuthorities);
  return newAuth;
 }
}

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
      @Autowired
      private MyUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter;
      @Override
      protected void configure(final HttpSecurity http) throws Exception {
          http.authorizeRequests()
          .antMatchers("/* ignored*/").permitAll()
          .antMatchers("/* ignored*/").access(Util.ROLE_PROVISIONER_OR_ADMIN)
          .antMatchers("/* ignored*/").access(Util.ROLE_ADMIN)
          .and().formLogin().loginPage("/login").failureUrl("/loginerror")
          .loginProcessingUrl("/j_spring_security_check").passwordParameter("j_password")
          .usernameParameter("j_username").defaultSuccessUrl("/index.html").and().logout()
          .logoutUrl("/j_spring_security_logout").logoutSuccessUrl("/loggedout")
          .deleteCookies("JSESSIONID", "SESSION")
          .and().sessionManagement().sessionFixation().migrateSession().maximumSessions(1)
          .and().and().addFilter(usernamePasswordAuthenticationFilter);
      }
      // check http://lifelongprogrammer.blogspot.com/2016/04/spring-security-integrate-in-memory.html
      // for implementation
      @Bean @Override
      public AuthenticationManager authenticationManagerBean() throws Exception {}
}

public class SupportedAppSecurityConfig implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private String adminLadpGroup;
    private String provisionLdapGroup;
}

Spring Security: Integrate In-Memory Authentication for Test Automation


Use Case
To automate test of our web services apis, we need add test users in test environments.

The Solution
We configure Spring Security AuthenticationManager: first add  in-memory DaoAuthenticationProvider in test environments then add other authentication providers.

The legacy code uses xml to configure authenticationManager and intercept-url rules, so I create org.springframework.security.authenticationManager bean. If you are using pure java config, please check Spring Security Java Config.
@Configuration
public class WebSecurityConfiguration {
    private static final String[] ENV_SUPPORT_TEST_USER = {PROFILE_LOCAL, PROFILE_XX};

    @Autowired
    private Environment environment;

    @Bean(name = "org.springframework.security.authenticationManager")
    public AuthenticationManager authenticationManager() {
        final List providers = new ArrayList<>();

        final String env = System.getProperty("env");
        if (StringUtils.isBlank(env)) {
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, "env is empty");
        }
        // The order matters: don't change the order - it will first try to use the test user
        // if not succeed then use ldap
        // add test user for local, q1, e1 only
        if (ArrayUtils.contains(ENV_SUPPORT_TEST_USER, env)) {
            addTestUserAuthProvider(providers);
        }

        if (PROFILE_LOCAL.equals(env) || PROFILE_DOCKER.equals(env)) {
            addLdapProviderForLocal(providers);
        } else {
            // application is deployed to aws, use different LdapAuthenticationProvider
            addLdapProviderForAwsEnv(providers);
        }

        final AuthenticationManager authenticationManager = new ProviderManager(providers);
        return authenticationManager;
    }

    protected void addTestUserAuthProvider(final List providers) {
        final DaoAuthenticationProvider testUserAuthProvider = new DaoAuthenticationProvider();
        final Collection users = new ArrayList<>();
        // add admin user
        UserDetails user = new User(environment.getProperty("spring.security.test.user.adminOnly.name"),
                environment.getProperty("spring.security.test.user.adminOnly.password"),
                Lists.newArrayList(new SimpleGrantedAuthority(environment.getProperty("spring.security.adminGroup"))));
        users.add(user);
        // add provisioner
        user = new User(environment.getProperty("spring.security.test.user.provisionerOnly.name"),
                environment.getProperty("spring.security.test.user.provisionerOnly.password"), Lists.newArrayList(
                        new SimpleGrantedAuthority(environment.getProperty("spring.security.provisionGroup"))));
        users.add(user);

        // add user with admin and provisioner
        user = new User(environment.getProperty("spring.security.test.user.adminProvisioner.name"),
                environment.getProperty("spring.security.test.user.adminProvisioner.password"),
                Lists.newArrayList(new SimpleGrantedAuthority(environment.getProperty("spring.security.adminGroup")),
                        new SimpleGrantedAuthority(environment.getProperty("spring.security.provisionGroup"))));
        users.add(user);

        final UserDetailsService userDetailsService = new InMemoryUserDetailsManager(users);
        testUserAuthProvider.setUserDetailsService(userDetailsService);

        providers.add(testUserAuthProvider);
    }
}

Testing with Rest-Assured
Now we can use Rest-Assured to login and capture the session and reuse it in subsequent requests.
RestAssured.reset();
RestAssured.baseURI = baseURI;
RestAssured.basePath = basePath;
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
RestAssured.config = config().logConfig(new LogConfig());
RestAssured.useRelaxedHTTPSValidation();

final SessionFilter sessionFilter = new SessionFilter();
given().auth()
.form(user, passwd,
        new FormAuthConfig("/j_spring_security_check", "j_username", "j_password"))
.filter(sessionFilter).expect().statusCode(200).when().get("/api");

// later...
given().filter(sessionFilter).contentType(ContentType.JSON).when().delete(productUrl).then()
        .statusCode(200);

Spring - Encrypt Properties by Customizing PropertySourcesPlaceholderConfigurer


Senario
Usually there are some sensitive properties(such as database password, aws key etc) in an application that we can't put it as plain text and push to git. We have to encrypt it, but decrypt when use it in the application.

Solution
We use some private password key to encrypt them, and put encrypted password in property file like below:
databse.password=ENC:encrypted_password

The ENC: prefix is used to tell the Spring application, this property is encrypted.

We pass the private password key to application server when start it by -DappPassword=password_key

Other approaches:
1. jasypt-spring-boot
You may consider to use jasypt-spring-boot in your sping-boot project. But I found one issue: By default it still decrypts the encrypted property every time when appContext.getEnvironment().getProperty is called for the same property.
com.ulisesbocchio.jasyptspringboot.resolver.DefaultPropertyResolver.resolvePropertyValue(String)

You may write your own MyEncryptablePropertyResolver to cache the already decrypted value in resolvePropertyValue.

2. PropertyPlaceholderConfigurer
Another option is to extend PropertyPlaceholderConfigurer
then implement methods convertPropertyValue, resolveSystemProperty, resolvePlaceholder to decrypt values.
The good part is it decrypt all values only once when spring creates PropertyPlaceholderConfigurer in PropertyResourceConfigurer.postProcessBeanFactory(ConfigurableListableBeanFactory). 

The bad part is that PropertyPlaceholderConfigurer is not EnvironmentAware which means we can not call appContext.getEnvironment().getProperty to get property value in static or non-spring-managed context.

Check the javadoc of PropertyPlaceholderConfigurer or PropertyResourceConfigurer:
As of Spring 3.1, PropertySourcesPlaceholderConfigurer should be used preferentially over this implementation; it is more flexible through taking advantage of the Environment and PropertySource mechanisms also made available in Spring 3.1.

How to Tell Spring to decrypt properties?
In Spring, we usually uses @PropertySource to specify property files. Then Spring uses PropertySourcesPlaceholderConfigurer to read them.

All we have to do is extend PropertySourcesPlaceholderConfigurer, so it(StringValueResolver) will decrypt property value when the values matches some pattern.

Problem of PropertySourcesPlaceholderConfigurer
One issue about PropertySourcesPlaceholderConfigurer: it handles @Value and appContext.getEnvironment().getProperty differently.

To decrypt value for placeholder in @Value, we can define our our StringValueResolver like below.

For @value, when spring tries to create the bean, it will call ValueResolver.resolveStringValue to parse it. We can define our our EncryptedValueResolver to decrypt value for placeholder.
EncryptedValueResolver.resolveStringValue(String) line: 32
DefaultListableBeanFactory(AbstractBeanFactory).resolveEmbeddedValue(String) line: 823
DefaultListableBeanFactory.doResolveDependency(DependencyDescriptor, String, Set, TypeConverter) line: 1084

DefaultListableBeanFactory.resolveDependency(DependencyDescriptor, String, Set, TypeConverter) line: 1064

But when you call appContext.getEnvironment().getProperty, the value is still not decrypted. One approach is to create one util SpringContextBridge, whose getProperty will decrypt the property value. 

Also we define our decrypt method to cache already decrypted value in a map.

The Implementation
First we register our custom EncryptedPropertySourcesPlaceholderConfigurer in configuration.
Notice it has to be static method, this bean has to be created first.
    @Bean
    public static PropertySourcesPlaceholderConfigurer properties() {
        final String password = System.getProperty(APP_ENCRYPTION_PASSWORD);
        if (StringUtils.isBlank(password)) {
            return new PropertySourcesPlaceholderConfigurer();
        } else {
            return new EncryptedPropertySourcesPlaceholderConfigurer(password);
        }
    }
Here we are using jasypt's BasicTextEncryptor, you are free to use any encryptor.
public class EncryptedPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer {
    private final String password;

    public EncryptedPropertySourcesPlaceholderConfigurer(final String password) {
        super();
        this.password = password;
    }

    @Override
    protected void doProcessProperties(final ConfigurableListableBeanFactory beanFactoryToProcess,
            final StringValueResolver valueResolver) {
        super.doProcessProperties(beanFactoryToProcess, new EncryptedValueResolver(valueResolver, password));
    }
}
public class EncryptedValueResolver implements StringValueResolver {

    public static final String ENCRYPTED_PREFIX = "ENC:";

    private StringValueResolver valueResolver;

    private static PBEStringEncryptor encryptor;

    // Here we can use different encryptor
    // don't use StrongTextEncryptor, unless u have installed the Java Cryptography
    // Extension (JCE) Unlimited Strength Jurisdiction Policy Files in this jvm.
    EncryptedValueResolver(final StringValueResolver stringValueResolver, final String password) {
        this.valueResolver = stringValueResolver;
        encryptor = getEncryptor(password);
    }

    @Override
    public String resolveStringValue(final String strVal) {

        // Values obtained from the property file to the naming
        // as seen with the encryption target
        String value = valueResolver.resolveStringValue(strVal);
        value = decrypt(value);
        return value;
    }

    private static Map<String, String> decryptValues = new HashMap<>();

    public static String decrypt(String originalValue) {
        if (originalValue != null && originalValue.startsWith(ENCRYPTED_PREFIX)) {
            return decryptValues.computeIfAbsent(originalValue,
                    oldValue -> encryptor.decrypt(oldValue.substring(ENCRYPTED_PREFIX.length())));
        }
        return originalValue;
    }

    private static final String SALT = "YOUR_SALT_HERE";

    public static StandardPBEStringEncryptor getEncryptor(final String password) {
        final StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setPassword(password); // we HAVE TO set a password
        // use default algorithm
        // don't use PBEWithMD5AndTripleDES
        encryptor.setAlgorithm("PBEWithMD5AndDES");

        final StringFixedSaltGenerator saltGenerator = new StringFixedSaltGenerator(SALT);
        encryptor.setSaltGenerator(saltGenerator);
        return encryptor;
    }

Problem in previous
There is one problem in previous code 


EncryptorUtil
Last, EncryptorUtil will use our private password key to encrypt text.
public class EncryptorUtil {
  protected static void decrypt(final String password, final String encryptedMessage) {
      final StandardPBEStringEncryptor encryptor = EncryptedValueResolver.getEncryptor(password);

      // don't use BasicTextEncryptor, as it's salt changes.
      // final BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
      // textEncryptor.setPassword(password);
      final String plainText = encryptor.decrypt(encryptedMessage);

      System.out.println("plainText: " + plainText);

  }

  protected static void encrypt(final String password, final String plainText) {
      final StandardPBEStringEncryptor encryptor = EncryptedValueResolver.getEncryptor(password);

      // don't use BasicTextEncryptor, as it's salt changes.
      // final BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
      // textEncryptor.setPassword(password);
      final String myEncryptedText = encryptor.encrypt(plainText);

      System.out.println("Encrypted value: " + EncryptedValueResolver.ENCRYPTED_PREFIX + myEncryptedText);
      // make sure we can decrypt from the encrypted text,
      System.out.println(
              "Decrypted value matches the actual value: " + plainText.equals(encryptor.decrypt(myEncryptedText)));
  }
  public static void main(final String[] args) {
       if (args.length < 3) {
       System.out.println("Please input the password(even length) and the text to be encrypted.");
       return;
       }
       final String action = args[0], password = args[1], text = args[2];
       if ("enc".equalsIgnoreCase(action)) {
       encrypt(password, text);
       } else {
       decrypt(password, text);
       }
  }
}
Resources
5.10. Properties Management
Extending Spring PropertyPlaceholderConfigurer to consider the OS Platform

Using Jackson JSON View to Protect Mass Assignment Vulnerabilities


Senario
We use JAX-RS to develop Restful Web Service and only consume and produce json data with Jackson.

In our model class(ModelA), there may be cases that:

  • Some fields are only viewable but not editable  - Client can view them but can't edit it, they are maintained by backend logic
  • Some fields are totally internal, shouldn't even return to client, and client is not allowed to edit.

We need use code to implement this logic, express what fields are viewable only, what fields are editable, and what fields are internal in whitelist mode; otherwise we may expose some security issue. - Check Mass-Assignment Vulnerabilities... Or How Github Got Hacked

Solution - Jackson @JsonView
We can create JSON view like below:
public class View {
    
    public static class Editable {}
    public static class Viewable extends Editable {}
    public static class Internal extends Viewable {}
}

Then annotate our mode class:
@JsonIgnoreProperties(ignoreUnknown = true)
public class Model implements Serializable {

 @JsonView(View.Editable.class)
 protected String editableField;

 @JsonView(View.Viewable.class)
 protected String viewableField; 

 @JsonView(View.Internal.class)
 protected String internalField;
}

At last, we annotate out jax-rs resource with @JsonView annotation.  
 @GET
 @Produces(MediaType.APPLICATION_JSON )
 @JsonView(View.Viewable.class)
 public Iterable<Model> search() {}

 @GET
 @Path("{id}")
 @Produces(MediaType.APPLICATION_JSON )
 @JsonView(View.Viewable.class)
 public Model getModel(@PathParam("id") final String id) {}

 @POST
 @Consumes({MediaType.APPLICATION_JSON})
 public Response add(@JsonView(View.Editable.class) final Model model) {}

In JAX-RS, if one model(either request or response) is annotated with @JsonView(View.Editable.class), in our case add method, Jackson will only serialize or deserialize fields that are annotated with @JsonView(View.Editable.class).
In our case, client can only pass editableField, if client pass any other fields, server will just silently ignore them.

If one model (either request or response) is annotated @JsonView(View.Viewable.class),  then Jackson will serialize or deserialize fields that are annotated with both @JsonView(View.Editable.class) and @JsonView(View.Viewable.class). child(Viewable) inherits view membership from parents(Editable).

In both cases, Jackson will not serialize or deserialize fields that are annotated with  @JsonView(View.Internal.class). So they are protected.

In our service implementation: in add method, we need make sure we add these non-editable fields; in update method, we may have to read and merge these non-editable fields from old value from database to the new value.

-- One trick: Don't mix-use @JsonIgnore and @JsonView, seems this will confuse Jackson, the field will be serialized or deserialized in all cases.

Misc
Spring MVC provides data binder that we can specify what fields are not allowed.
@InitBinder public void initBinder(WebDataBinder binder) { binder.setDisallowedFields(DISALLOWED_FIELDS); }


Read More
Jackson Essentials - the JSON Libaray
Using Jackson JSON View to Protect Mass Assignment Vulnerabilities
Merge JSON Objects: Jackson + BeanUtils.copyProperties
Jackson Generic Type + Java Type Erasure

Jackson Date Serialize + Deserialize
http://wiki.fasterxml.com/JacksonJsonViews
Mass-Assignment Vulnerabilities... Or How Github Got Hacked
Mass Assignment, Rails, and You

Configure Tomcat SSL Using PFX(PKCS12) Certificate


I am trying to import certificate from entrust to tomcat.
Entrust provides a pfk file to us. pfx means Personal Information Exchange, it stores many cryptography objects as a single file. Read more about PKCS #12

To import the  pfx(PKCS_12) to tomcat or other java web server, the easy solution is to convert the pfx(PKCS_12) file to Java Key Store file.
1. Using keytool
Since JDK6, we can use JDK keytool to convert pkcs12 to JKS.
keytool -importkeystore -srckeystore file.pfx -srcstoretype PKCS12 -destkeystore cert.jks -deststoretype JKS
2. Using XWSS
For older JDK, we can use XWSS utility to convert pkcs12 to JKS.
XWSS - XML and WebServices Security Project is part of Project Metro in the Glassfish community. It provide some utility that can be downloaded from here.

Download the pkcs12import.zip, unzip it, we can find pkcs12import.bat.
pkcs12import usage
pkcs12import -file pkcs12-file [ -keystore keystore-file ]
[ -pass pkcs12-password ]   [ -storepass store-password ]  [ -keypass key-password ] [ -alias alias ]

Add SSL Connector in server.xml

 
Restart tomcat, and try to access https://localhost/
Resources
Keytool
PKCS 12 Wiki
Converting .pfx Files to .jks Files
How to import PFX file into JKS using pkcs12import utility

Extend Waffle to Limit Which Window User can Access


We want to use windows integrated authentication to authenticate user and only allow user who started the application to access it. 

We can easily implement this by extending Waffle.
Waffle can do windows integrated authentication for us, after that we just need check whether the user name and domain of the logged-on user is same as the account who starts the application.
Implementation Code
After waffle.servlet.NegotiateSecurityFilter, the following filter would check whether user name and domain of the remote user and the user who starts the web application matches.
The complete code can be found at Github.
We can get the user who starts the web application by the following code:
NTSystem system = new NTSystem();
String runasUser = system.getName();
String runasDomain = system.getDomain();

There are several ways to get user name and domain of remote logged-on user:
1.  waffle.servlet.NegotiateSecurityFilter save waffle.servlet.WindowsPrincipal instance in session. We can get user name and domain info from WindowsPrincipal.
request.getSession().setAttribute(PRINCIPAL_SESSION_KEY,windowsPrincipal);
2.  waffle.servlet.NegotiateSecurityFilter add windowsPrincipal into subject, and save subject into session. Form subject instance, we can get needed info.
subject.getPrincipals().add(windowsPrincipal);
session.setAttribute("javax.security.auth.subject", subject);

package src.main.java.org.codeexample.jeffery.misc.waffle;

import waffle.servlet.NegotiateSecurityFilter;
import waffle.servlet.WindowsPrincipal;
import com.sun.jna.platform.win32.Secur32;
import com.sun.jna.platform.win32.Secur32Util;
import com.sun.security.auth.module.NTSystem;

public class OnlyAllowUserStartItFilter implements Filter {
  protected static final Logger logger = LoggerFactory
      .getLogger(NegotiateSecurityFilter.class);
  private static final String PRINCIPAL_SESSION_KEY = NegotiateSecurityFilter.class
      .getName() + ".PRINCIPAL";
  
  @Override
  public void doFilter(ServletRequest sreq, ServletResponse sres,
      FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) sreq;
    boolean valid = false;    
    HttpSession session = request.getSession(false);
    if (session != null) {
      WindowsPrincipal winPrincipal = (WindowsPrincipal) session
          .getAttribute(PRINCIPAL_SESSION_KEY);
      valid = validateRemoteUser(winPrincipal);
    }
    if (!valid) {
      sendUnauthorized(sres);
    } else {
      chain.doFilter(sreq, sres);
    }
  }
  private boolean validateRemoteUserViaWinPrincipal(Subject subject) {
    boolean valid = false;
    Set<Principal> principals = subject.getPrincipals();
    WindowsPrincipal winPrincipal = null;
    for (Principal principal : principals) {
      if (principal instanceof WindowsPrincipal) {
        winPrincipal = (WindowsPrincipal) principal;
      }
    }
    valid = validateRemoteUser(winPrincipal);
    return valid;
  }
  private boolean validateRemoteUser(WindowsPrincipal winPrincipal) {
    boolean valid = false;
    if (winPrincipal != null) {
      String fqn = winPrincipal.getName();
      int atIdx = fqn.indexOf('\\');
      String remoteDomain = null, remoteUser = null;
      if (atIdx > -1) {
        remoteDomain = fqn.substring(0, atIdx);
        remoteUser = fqn.substring(atIdx + 1);
      } else {
        remoteUser = fqn;
      }
      NTSystem system = new NTSystem();
      valid = validDomain(remoteDomain, system)
          && validateUser(remoteUser, system);
    }
    return valid;
  }
  private boolean validateRemoteUserViaSecur32() {
    boolean valid = false;
    String remoteUserInfo = Secur32Util
        .getUserNameEx(Secur32.EXTENDED_NAME_FORMAT.NameSamCompatible);
    if (remoteUserInfo != null) {
      String remoteDomain = null, remoteUser = null;
      if (atIdx > -1) {
        remoteDomain = remoteUserInfo.substring(0, atIdx);
        remoteUser = remoteUserInfo.substring(atIdx + 1);
      } else {
        remoteUser = remoteUserInfo;
      }
      
      NTSystem system = new NTSystem();
      valid = validDomain(remoteDomain, system)
          && validateUser(remoteUser, system);
    }
    return valid;
  }
  
  private boolean validateUser(String remoteUser, NTSystem system) {
    boolean valid = false;
    String runasUser = system.getName();
    if (runasUser != null) {
      if (runasUser.equals(remoteUser)) {
        valid = true;
      }
    } else {
      // this is unlikely to happen
      logger.error("runasUser is null, remoteUser: " + remoteUser);
      if (remoteUser == null) {
        valid = true;
      }
    }
    return valid;
  }
  private boolean validDomain(String remoteDomain, NTSystem system) {
    boolean valid = false;
    String runasDomain = system.getDomain();
    if (runasDomain != null) {
      if (runasDomain.equalsIgnoreCase(remoteDomain)) {
        valid = true;
      }
    } else {
      if (remoteDomain == null) {
        valid = true;
      }
    }
    return valid;
  }
  private void sendUnauthorized(ServletResponse sres) throws IOException {
    HttpServletResponse response = (HttpServletResponse) sres;
    response.setHeader("Connection", "close");
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
        "This application can be only accessed by user who started it.");
    response.flushBuffer();
  }
}
Define waffle.servlet.NegotiateSecurityFilter and OnlyAllowUserStartItFilter
Next, we need define these 2 filters in web.xml, we need make sure we first define waffle.servlet.NegotiateSecurityFilter, then define OnlyAllowUserStartItFilter, to make NegotiateSecurityFilter run first, then run OnlyAllowUserStartItFilter.

As we are using jetty, and all applications in the jetty need this feature, we define these 2 filters in our own webdefault.xml.

Java Http Authentication


Scenario: Want to access protected resource in remote machine. There are several ways to do this.

1. If credential of the current logged-on user can be used to access the remote protected resource, then there is no need to add user/password info explicitly: Java URLConnection can automatically do this for me.

Apache HttpClient is a great tool to execute http requests, and add authentication, but it doesn't support to automatically authentication using current logged-on user credential. So sometimes, we have to use Java UrlConnection instead of Apache http client libaray.
Please refer to: http://httpcomponents.10934.n7.nabble.com/Authenticate-Proxy-using-currently-logged-on-domain-user-s-credentials-td11338.html
2. Use Authenticator.setDefault
Authenticator.setDefault(new Authenticator() {
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication (username, password.toCharArray());
    }
});
This sets default Authenticator which is called whenever authentication is required for any URLConnection.

This works for both basic form authentication. 
If you want to use a domain user/passwword to do login(NTLM widows integrated authentication), just use:
return new PasswordAuthentication(domain + "\\" + userName, password.toCharArray());

The problem in java is that Authenticator.setDefault() setups an authenticator for all HttpURLConnection, there is no such a method setAuthenticator on URLConnection.
3. Sending Basic authentication using URLConnection

http://blogs.deepal.org/2008/01/sending-basic-authentication-using-url.html
String authorizationString = “Basic “ + Base64.encode(username:password);
urlConnection.setRequestProperty ("Authorization", authorizationString)
Http Negotiate (SPNEGO) Example
SPNEGO is used to negotiate one of a number of possible real mechanisms. SPNEGO is used when a client application wants to authenticate to a remote server, but neither end is sure what authentication protocols the other supports. The pseudo-mechanism uses a protocol to determine what common GSSAPI mechanisms are available, selects one and then dispatches all further security operations to it. This can help organizations deploy new security mechanisms in a phased manner.

Security in Server Side
Http Debug
1 Use Fiddler to log traffic between client and sever.
http://blog.alner.net/archive/2008/10/06/fiddler-ndash-put-a-breakpoint-in-your-network-traffichellip.aspx
http://blog.alner.net/archive/2008/10/03/use-fiddler-to-view-traffic-when-running-locallyhellip.aspx
2 Change Java Class Log level
For this, we want to change the log level of 
-Djava.util.logging.config.file=logging.properties

In logging.properties
handlers=java.util.logging.ConsoleHandler
.level=ALL
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
sun.net.www.protocol.http.HttpURLConnection.level = ALL
java.net.URLConnection.level = ALL
Main Classes
sun.net.www.protocol.http.HttpURLConnection.getInputStream()
sun.net.www.protocol.http.AuthenticationHeader.parse()
java.net.Authenticator.requestPasswordAuthentication
sun.net.www.protocol.http.spnego.NegotiateCallbackHandler.handle(Callback[])

Other Resources
Authentication scheme
Basic, Digest, NTLM, Http Negotiate (SPNEGO)
Scheme Preference
GSS/SPNEGO -> Digest -> NTLM -> Basic

Labels

ANT (6) Algorithm (69) Algorithm Series (35) Android (7) Big Data (7) Blogger (14) Bugs (6) Cache (5) Chrome (19) Code Example (29) Code Quality (7) Coding Skills (5) Database (7) Debug (16) Design (5) Dev Tips (63) Eclipse (32) Git (5) Google (33) Guava (7) How to (9) Http Client (8) IDE (7) Interview (88) J2EE (13) J2SE (49) JSON (7) Java (186) JavaScript (27) Learning code (9) Lesson Learned (6) Linux (26) Lucene-Solr (112) Mac (10) Maven (8) Network (9) Nutch2 (18) Performance (9) PowerShell (11) Problem Solving (11) Programmer Skills (6) Scala (6) Security (9) Soft Skills (38) Spring (22) System Design (11) Testing (7) Text Mining (14) Tips (17) Tools (24) Troubleshooting (29) UIMA (9) Web Development (19) Windows (21) adsense (5) bat (8) regex (5) xml (5)