{"id":16118946,"url":"https://github.com/cch0/spring-boot-pre-authentication","last_synced_at":"2025-03-18T10:31:19.317Z","repository":{"id":84547355,"uuid":"195426197","full_name":"cch0/spring-boot-pre-authentication","owner":"cch0","description":"spring boot pre-authentication with spring security","archived":false,"fork":false,"pushed_at":"2019-07-09T20:34:11.000Z","size":24,"stargazers_count":11,"open_issues_count":0,"forks_count":7,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-28T09:11:23.550Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cch0.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-05T14:50:32.000Z","updated_at":"2024-07-19T22:25:31.000Z","dependencies_parsed_at":"2023-03-11T06:46:04.889Z","dependency_job_id":null,"html_url":"https://github.com/cch0/spring-boot-pre-authentication","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cch0%2Fspring-boot-pre-authentication","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cch0%2Fspring-boot-pre-authentication/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cch0%2Fspring-boot-pre-authentication/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cch0%2Fspring-boot-pre-authentication/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cch0","download_url":"https://codeload.github.com/cch0/spring-boot-pre-authentication/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243922011,"owners_count":20369334,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-09T20:51:40.034Z","updated_at":"2025-03-18T10:31:19.312Z","avatar_url":"https://github.com/cch0.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Securing Spring Boot Application With Spring Security\n\n # Goal\n \n The goal is to provide a working example to illustrate how to secure a Spring Boot application using Spring Security.\n \n \n # Example\n \n The application contains two endpoints, one is secured and requires caller to provide information for authorization \n to access the endpoint. Authentication is assumed to have been done and information is provided through HTTP request headers. \n This is also called [Pre-Authentication Scenarios](https://docs.spring.io/spring-security/site/docs/5.2.x/reference/html5/#preauth)\n \n \u003eThere are situations where you want to use Spring Security for authorization, but the user has already been reliably authenticated by some external system prior to accessing the application. We refer to these situations as \"pre-authenticated\" scenarios\n \n This use case can be useful when user authentication is already performed through other mechanism such as X.509 certificate.\n \n # Build and Run the Application\n \n```\nmvn clean verify\n\njava -jar target/spring-boot-pre-authentication-0.0.1-SNAPSHOT.jar\n\n```\n \n# Endpoints\n\n## Service Endpoints\n## /v1/hello\n\nThis is a secure endpoint and requires caller to have `ADMIN` role.\n\n## /v1/bye\n \nThis is an unsecure endpoint and does not require caller to be associated with any role.\n \n## Actuator Endpoints\n\nWe would like these endpoints to remain accessible without authentication or authorization  \n \n# Spring Security Configuration\n \n## maven pom.xml \n\n```\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n    \u003cartifactId\u003espring-boot-starter-security\u003c/artifactId\u003e\n\u003c/dependency\u003e\n``` \n \n## Method Level Security Configuration\n\nMethod level security configuration offers greater flexibility compared to configuration through bean.\n\nFirst thing to do is to enable global method security\n\n```\n@Configuration\n@EnableGlobalMethodSecurity(\n        prePostEnabled = true,\n        securedEnabled = true,\n        jsr250Enabled = true)\npublic class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {\n}\n```\n\nThen, we would like to use meta-annotation which allows us to decouple the business logic from underlying security \nimplementation.\n\nIn the following example, we define **isAdmin** annotation which can be used in any places where admin role is needed in\norder to access the endpoint. What it translates into is for caller to have the **ADMIN** role in the system. Later on \nif rule has changed to also allow user with other role to also access the resources, then this is the only place where we need\nto make the change. \n\n```\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@PreAuthorize(\"hasRole('ADMIN')\")\npublic @interface IsAdmin {\n}\n```\n\nUsing custom meta-annotation is not the only way to provide method level security but it does provide the greatest\nflexibility over other pre-defined annotations.\n\n\n## Spring Security Configuration\n\nWe create a custom configuration class which extends existing **WebSecurityConfigurerAdapter** to customize endpoints \nfor our need. We also specify using **RequestHeaderAuthenticationFilter** to allow us to extract authentication information\nfrom the HTTP headers in order to determine the level of authorization a user has.\n\nWe also allow access to actuator endpoints, static asset and all the endpoints which has `v1` as prefix. All other endpoints\naccess are denied regardless if authentication information is provided or not. Because of the authentication filter, all allowed endpoints are still going through the authentication\nfilter but for actuator and static assets, since normally authentication information is not provided in the HTTP headers,\nuser information will not be loaded from data store.  \n\n```java\n\n@Configuration\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        http\n                // configure pre-authentication flow\n                .addFilterBefore(siteminderFilter(), RequestHeaderAuthenticationFilter.class)\n                .authorizeRequests()\n                // allow access to actuator endpoints\n                .antMatchers(\"/actuator/**\").permitAll()\n                // allow access to favicon.ico endpoint\n                .antMatchers(\"/favicon.ico\").permitAll()\n                // allow access to v1 endpoints, still subject to authorization check defined on the method level\n                .antMatchers(\"/v1/**\").permitAll()\n                // deny all other endpoints\n                .anyRequest().denyAll()\n        ;\n    }\n    \n    \n```\n\nInside the same **SecurityConfig** class, the following configuration of authentication filter specifies which HTTP header is for communicating principal information\nand which header is for credential. Principal header is required while credential is optional. We also disable throwing\nexception when headers are not provided. This is needed for actuator and static asset where headers are normally\nnot provided. Otherwise, we won't be able to access actuator endpoints and static asset.\n\n```java\n\npublic RequestHeaderAuthenticationFilter siteminderFilter() {\n    RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();\n\n    // set the header name which provides principal information\n    requestHeaderAuthenticationFilter.setPrincipalRequestHeader(\"x-actor-id\");\n\n    // set the header name which provides credential information\n    requestHeaderAuthenticationFilter.setCredentialsRequestHeader(\"x-grantor-id\");\n\n    requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager());\n\n    // do not throw exception when header is not present.\n    // one use case is for actuator endpoints and static assets where security headers are not required.\n    requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(false);\n\n    return requestHeaderAuthenticationFilter;\n}\n```\n\nWe create a class which implements **AuthenticationUserDetailsService** interface and provide implementation of\nhow user information is loaded from data store using authentication information. The following example is NOT complete\nin the sense that data access part is not added but rather hard-coded. The main purpose of this example is to show\nhow to configure role information a user has. \n\n```java\n\npublic class AuthorizationUserDetailsService implements AuthenticationUserDetailsService\u003cPreAuthenticatedAuthenticationToken\u003e {\n\n    /**\n     * Loads user from data store and creates UserDetails object based on principal and/or credential.\n     *\n     * Role name needs to have \"ROLE_\" prefix.\n     *\n     * @param token instance of PreAuthenticatedAuthenticationToken\n     * @return UserDetails object which contains role information for the given user.\n     * @throws UsernameNotFoundException\n     */\n    @Override\n    public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) throws UsernameNotFoundException {\n        final String principal = (String)token.getPrincipal();\n        final String credential = (String)token.getCredentials();\n\n        // TODO this is only for illustration purpose. Should retrieve user from data store and determine user roles\n        if (principal.equals(\"joe\")) {\n            // TODO some user lookup and then create User object with roles\n\n            return new User(\"admin-user\", \"\", Collections.singletonList(new SimpleGrantedAuthority(\"ROLE_ADMIN\")));\n        } else {\n            return new User(\"normal-user\", \"\", Collections.singletonList(new SimpleGrantedAuthority(\"ROLE_USER\")));\n        }\n    }\n}\n``` \n\n## Endpoints Configuration\n\n**/v1/hello** endpoint has **@IsAdmin** annotation and user needs to have the **ADMIN** role in order to access that endpoint.\n\n**/v1/bye** endpoint, on the other hand, is accessible without needing the authorization.\n\n\n```java\n@RestController\n@RequestMapping(value = \"/v1\")\npublic class Controller {\n\n    /**\n     * Endpoint which requires ADMIN role to access.\n     * @return\n     */\n    @RequestMapping({ \"/hello\" })\n    @IsAdmin\n    public String hello() {\n        return \"Hello World\";\n    }\n\n    /**\n     * Endpoint which does not require any authorization.\n     * @return\n     */\n    @RequestMapping({ \"/bye\" })\n    public String bye() {\n        return \"bye\";\n    }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcch0%2Fspring-boot-pre-authentication","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcch0%2Fspring-boot-pre-authentication","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcch0%2Fspring-boot-pre-authentication/lists"}