1. Overview

In this tutorial, we’ll have a look into Java’s built-in security infrastructure, which is disabled by default. Specifically, we’ll examine its main components, extension points, and configurations.

2. SecurityManager in Action

It might be a surprise, but default SecurityManager settings disallow many standard operations:

System.setSecurityManager(new SecurityManager());
new URL("http://www.google.com").openConnection().connect();

Here, we programmatically enable security supervision with default settings and attempt to connect to google.com.

Then we get the following exception:

java.security.AccessControlException: access denied ("java.net.SocketPermission"
  "www.google.com:80" "connect,resolve")

There are numerous other use-cases in the standard library — for example, reading system properties, reading environment variables, opening a file, reflection, and changing the locale, to name a few.

3. Use-Case

This security infrastructure has been available since Java 1.0. This was a time where applets – Java applications embedded into the browser – were pretty common. Naturally, it was necessary to constrain their access to system resources.

Nowadays, applets are obsolete. However, security enforcement is still an actual concept when there is a situation in which third-party code executes in a protected environment.

For example, consider that we have a Tomcat instance where third-party clients may host their web applications. We don’t want to allow them to execute operations like System.exit() because that would affect other applications and possibly the whole environment.

The Java Security Manager was deprecated for removal in Java 17, mainly due to its complexity and the challenges it presented for effective security management. While it was designed to provide fine-grained access controls for Java applications, its usage often led to cumbersome configurations and a steep learning curve for developers.

Moreover, modern applications and systems have moved towards other security mechanisms, such as code signing, containerization, and operating system-level security features, which are considered more effective and easier to manage. Over time, the SecurityManager became less relevant for most use cases and was seen as an outdated approach to securing Java applications, leading to its deprecation.

4. Design

4.1. SecurityManager

One of the main components in the built-in security infrastructure is java.lang SecurityManager. It has several checkXxx methods like checkConnect, which was authorizing our attempt to connect to Google in the test above. All of them delegates to the checkPermission(java.security.Permission) method.

4.2. Permission

java.security.Permission instances stand for authorization requests. Standard JDK classes create them for all potentially dangerous operations (like reading/writing a file, opening a socket, etc.) and give them over to SecurityManager for proper authorization.

4.3. Configuration

We define permissions in a special policy format. These permissions take the form of grant entries:

grant codeBase "file:${{java.ext.dirs}}/*" {
    permission java.security.AllPermission;
};

The codeBase rule above is optional. We can specify no field at all there or use signedBy (integrated with corresponding certificates in the keystore) or principal (java.security.Principal attached to the current thread via javax.security.auth.Subject). We can use any combination of those rules.

By default, the JVM loads the common system policy file located at <java.home>/lib/security/java.policy. If we’ve defined any user-local policy in  <user.home>/.java.policy, the JVM appends it to the system policy.

It’s also possible to specify policy file via command line: –Djava.security.policy=/my/policy-file. That way we can append policies to the previously loaded system and user policies.

There is a special syntax for replacing all system and user policies (if any) – double equals sign: –Djava.security.policy==/my/policy-file

5. Example

Let’s define a custom permission:

public class CustomPermission extends BasicPermission {
    public CustomPermission(String name) {
        super(name);
    }

    public CustomPermission(String name, String actions) {
        super(name, actions);
    }
}

and a shared service that should be protected:

public class Service {

    public static final String OPERATION = "my-operation";

    public void operation() {
        SecurityManager securityManager = System.getSecurityManager();
        if (securityManager != null) {
            securityManager.checkPermission(new CustomPermission(OPERATION));
        }
        System.out.println("Operation is executed");
    }
}

If we try to run it with a security manager enabled, an exception is thrown:

java.security.AccessControlException: access denied
  ("com.baeldung.security.manager.CustomPermission" "my-operation")

    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
    at java.security.AccessController.checkPermission(AccessController.java:884)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
    at com.baeldung.security.manager.Service.operation(Service.java:10)

We can create our <user.home>/.java.policy file with the following content and try re-running the application:

grant codeBase "file:<our-code-source>" {
    permission com.baeldung.security.manager.CustomPermission "my-operation";
};

It works just fine now.

6. Conclusion

In this article, we checked how the built-in JDK security system is organized and how we can extend it. Even though the target use-case is relatively rare, it’s good to be aware of it.

As usual, the complete source code for this article is available over on GitHub.