{"id":21901578,"url":"https://github.com/amadr-95/spring-security","last_synced_at":"2025-03-22T06:17:51.792Z","repository":{"id":239999546,"uuid":"801403079","full_name":"amadr-95/spring-security","owner":"amadr-95","description":"Spring security course. Basic auth, permission and form based auth, JWT","archived":false,"fork":false,"pushed_at":"2024-05-16T07:25:26.000Z","size":440,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-27T06:44:05.036Z","etag":null,"topics":["auth","java","jwt","spring-security"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amadr-95.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2024-05-16T06:57:33.000Z","updated_at":"2024-05-16T07:26:16.000Z","dependencies_parsed_at":"2024-05-16T08:26:51.287Z","dependency_job_id":"47492f81-23f6-47a0-b33e-10e189bdea7f","html_url":"https://github.com/amadr-95/spring-security","commit_stats":null,"previous_names":["amadr-95/spring-security"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amadr-95%2Fspring-security","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amadr-95%2Fspring-security/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amadr-95%2Fspring-security/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amadr-95%2Fspring-security/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amadr-95","download_url":"https://codeload.github.com/amadr-95/spring-security/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244913332,"owners_count":20530818,"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":["auth","java","jwt","spring-security"],"created_at":"2024-11-28T15:14:22.292Z","updated_at":"2025-03-22T06:17:51.772Z","avatar_url":"https://github.com/amadr-95.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spring Security\r\n\r\nSpring Security allows to secure the access to API endpoints and\r\ngrant priviliges only to trusted users or permissions.\r\nIn this example we start from a API already built.\r\n\r\n## ApplicationSecurityConfig class\r\n\r\nUnder the package `com.spring.security` is the class where all configurations about security are done\r\n\r\n```java\r\npackage com.spring.spring.security;\r\n\r\nimport org.springframework.context.annotation.Configuration;\r\nimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;\r\n\r\n@Configuration\r\n@EnableWebSecurity\r\npublic class ApplicationSecurityConfig {\r\n}\r\n```\r\n\r\n### User Roles and Permissions (Authorities)\r\n\r\nWe can create **roles** and **permissions** as follows:\r\n\r\n#### Permissions\r\n\r\n```java\r\npublic enum ApplicationUserPermission {\r\n    STUDENT_READ(\"student:read\"),\r\n    STUDENT_WRITE(\"student:write\"),\r\n    STUDENT_DELETE(\"student:delete\"),\r\n    STUDENT_UPDATE(\"student:update\");\r\n\r\n    private final String permission;\r\n\r\n    ApplicationUserPermission(String permission) {\r\n        this.permission = permission;\r\n    }\r\n\r\n    public String getPermission() {\r\n        return permission;\r\n    }\r\n}\r\n```\r\n\r\n#### Roles\r\n\r\nOnce permissions are stablished, they can be assigned to roles. So in\r\nthis manner a specific role can have a set of permissions.\r\n\r\n```java\r\npublic enum ApplicationUserRole {\r\n    STUDENT(Set.of(STUDENT_READ, STUDENT_UPDATE)),\r\n    ADMIN(Set.of(STUDENT_READ, STUDENT_WRITE, STUDENT_DELETE, STUDENT_UPDATE));\r\n\r\n    private final Set\u003cApplicationUserPermission\u003e permissions;\r\n\r\n    ApplicationUserRole(Set\u003cApplicationUserPermission\u003e permissions) {\r\n        this.permissions = permissions;\r\n    }\r\n\r\n    public Set\u003cApplicationUserPermission\u003e getPermissions() {\r\n        return permissions;\r\n    }\r\n}\r\n```\r\n\r\n## Types of Auth\r\n\r\n### Basic Auth\r\n\r\nIn this method the client send the user and password in\r\nevery request, which are encrypted by Basic64.\r\nThere is no posibility to logout\r\n(mostly used with external API's)\r\n\r\n![basic-auth](https://github.com/amadr-95/spring-security/assets/122611230/21434277-f472-4c33-9294-e1aa559f0eef)\r\n\r\n#### Role based Authentication\r\n\r\nGranting access to API endpoints filtering by **user role**.\r\n\r\n\u003e [!WARNING]\r\n\u003e **Cross Site Request Forgery**\r\n\u003e The action of forging a copy or imitation of a document, signature,\r\n\u003e banknote, or work of art.\r\n\u003e Spring Security try to protect teh API by default.\r\n\u003e On POST, PUT, DELETE methods the request is rejected\r\n\u003e because no token was sent to the client when logged in. That is\r\n\u003e what csrf does by default when it is enabled.\r\n\u003e \r\n\u003e ![crsf](https://github.com/amadr-95/spring-security/assets/122611230/c9aad1fc-a43b-4c70-82df-fbc652c26d46)\r\n\u003e \r\n\u003e [CSRF protection is recommended](https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/csrf.html) to use for any request that could be\r\n\u003e processed by a browser by normal users. If you are only creating a service that is used by non-browser clients, you\r\n\u003e will\r\n\u003e likely want to disable CSRF protection. So in this case we can disable it.\r\n\r\n```java\r\n@Bean\r\npublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {\r\n    return httpSecurity\r\n            .authorizeHttpRequests(authz -\u003e authz\r\n                    .csrf(AbstractHttpConfigurer::disable)\r\n                    .requestMatchers(\"/\", \"index.html\", \"/css/*\", \"/js/*\")\r\n                    .permitAll()\r\n                    .requestMatchers(\"/api/**\").hasRole(ADMIN.toString())\r\n                    .anyRequest()\r\n                    .authenticated()\r\n            )\r\n            .httpBasic(Customizer.withDefaults())\r\n            .build();\r\n}\r\n```\r\n\r\nCreating the user using `InMemoryUserDetailsManager` with its role (ADMIN).\r\n\r\n```java\r\n@Bean\r\npublic InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {\r\n    UserDetails admin = User.builder()\r\n            .username(\"admin\")\r\n            .password(passwordEncoder.encode(\"password\"))\r\n            .roles(\"ADMIN\")\r\n            .build();\r\n    return new InMemoryUserDetailsManager(admin);\r\n}\r\n```\r\n\r\n\u003e [!NOTE]\r\n\u003e The password must be encoded\r\n\r\n#### Permission based Authentication\r\n\r\nGranting access to API endpoints filtering by **user's permissions** instead of the role itself.\r\n\r\nNow we have to change `.role(ROLE)` for `.authorities(authorities)`:\r\n\r\n```java\r\n\r\n@Bean\r\npublic InMemoryUserDetailsManager userDetailsManager(PasswordEncoder passwordEncoder) {\r\n    UserDetails studentUser = User.builder()\r\n            .username(\"student\")\r\n            .password(passwordEncoder.encode(\"student\"))\r\n            //.roles(STUDENT.toString()) //ROL_STUDENT\r\n            .authorities(STUDENT.getGrantedAuthorities())\r\n            .build();\r\n\r\n    UserDetails adminUser = User.builder()\r\n            .username(\"admin\")\r\n            .password(passwordEncoder.encode(\"admin\"))\r\n            //.roles(ADMIN.toString()) //ROL_ADMIN\r\n            .authorities(ADMIN.getGrantedAuthorities())\r\n            .build();\r\n    return new InMemoryUserDetailsManager(studentUser, adminUser);\r\n}\r\n```\r\n\r\nTo be able to obtain permissions from an user, we have to convert permissions from plain text to\r\na type that implements `GrantedAuthority` interface, such as `SimpleGrantedAuthority`:\r\n\r\n```java\r\npublic Set\u003cSimpleGrantedAuthority\u003e getGrantedAuthorities() {\r\n    Set\u003cSimpleGrantedAuthority\u003e grantedAuthorities = new HashSet\u003c\u003e();\r\n    //convert every ApplicationUserPermission to a SimpleGrantedAuthority\r\n    for (ApplicationUserPermission permission : permissions) {\r\n        grantedAuthorities.add(\r\n                new SimpleGrantedAuthority(permission.getPermission())\r\n        );\r\n    }\r\n    //Add role\r\n    grantedAuthorities.add(new SimpleGrantedAuthority(\"ROLE_\" + this));\r\n\r\n    return grantedAuthorities;\r\n}\r\n```\r\n\r\nBy doing this, we have attached the correct authorities to users.\r\n\r\n#### Permission based Authentication on a method level\r\n\r\nWe can also set authentication by annotating methods with `@PreAuthorized()`.\r\nWithin the annotation we can both filter by roles or permissions passing it these strings:\r\n\r\n- hasRole('ROLE_')\r\n- hasAnyRole('ROLE_')\r\n- hasAuthority('permission')\r\n- hasAnyAuthority('permission')\r\n\r\nTo use that we also have to annotate `ApplicationSecurityConfig` with `@EnableMethodSecurity`\r\n\r\n```java\r\n\r\n@GetMapping\r\n@PreAuthorize(\"hasRole('ADMIN')\")\r\npublic List\u003cStudent\u003e findAllStudents() {\r\n    return studentService.findAllStudents();\r\n}\r\n\r\n@GetMapping(\"{id}\")\r\n@PreAuthorize(\"hasAnyRole('ADMIN', 'STUDENT')\")\r\npublic Student findStudentById(@PathVariable(\"id\") Integer studentId) throws StudentNotFoundException {\r\n    return studentService.findStudentById(studentId);\r\n}\r\n\r\n@PostMapping\r\n@PreAuthorize(\"hasAuthority('student:write')\")\r\npublic void addNewStudent(@RequestBody StudentRequest student) throws StudentException {\r\n    studentService.addNewStudent(student);\r\n}\r\n```\r\n\r\n### Form based Authentication\r\n\r\nForm based is the default type of auth that comes with Spring security.\r\nIt auto-generates a form to login with user credentials. Once logged,\r\na cookie `SESSIONID` (generated when the user login for first time) is sending\r\nto the server on every request.\r\n\r\nEnabling form auth instead of basic auth is just a matter of changing one line:\r\n\r\n```java\r\n\r\n@Bean\r\npublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {\r\n    return httpSecurity\r\n            .authorizeHttpRequests(authz -\u003e authz\r\n                    ...\r\n            )\r\n            .formLogin(Customizer.withDefaults())\r\n            .build();\r\n}\r\n```\r\n\r\n![form-auth](https://github.com/amadr-95/spring-security/assets/122611230/136cb7e8-8fbc-4e14-84aa-f662ad893ca0)\r\n\r\n\r\n#### Options\r\n\r\n* Custom login page\r\n    ```java\r\n    .formLogin(httpSecurityFormLoginConfigurer -\u003e httpSecurityFormLoginConfigurer\r\n        .passwordParameter(\"password\") //same name as form name in html\r\n        .usernameParameter(\"username\")\r\n        .loginPage(\"/login\")\r\n        .permitAll()\r\n        .defaultSuccessUrl(\"url\", true)\r\n    )\r\n    ```\r\n\r\n* Custom remember me expiration time  \r\n  By default SESSIONID cookie expires after 30' of inactivity.\r\n  These can be changed as follows:\r\n    ```java\r\n    //.rememberMe(Customizer.withDefaults()) //valid 30'\r\n    .rememberMe(httpSecurityRememberMeConfigurer -\u003e httpSecurityRememberMeConfigurer\r\n        .rememberMeParameter(\"remember-me\")  \r\n        .tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(21))\r\n        .key(\"securekey\") //key to encrypt the username and expiration time instead of default one\r\n    ) \r\n    ```\r\n* Custom logout\r\n    ```java\r\n    .logout(httpSecurityLogoutConfigurer -\u003e httpSecurityLogoutConfigurer\r\n        .logoutUrl(\"/logout\")\r\n        .clearAuthentication(true)\r\n        .deleteCookies(\"JSESSIONID\", \"remember-me\")\r\n        .logoutSuccessUrl(\"/index.html\")\r\n    )\r\n    ```\r\n\r\n## JWT (JSON Web Token)\r\n\r\n![jwt_diagram](https://github.com/amadr-95/spring-security/assets/122611230/00df9226-b745-432d-b726-26b9a2a6d6bd)\r\n\r\n### Java JWT Library\r\n\r\nManaging authentication requests based on username and password using JSON Web Tokens (JWT).\r\nTo use these library we have to add the dependecies to `pom.xml`:\r\n\r\n```xml\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eio.jsonwebtoken\u003c/groupId\u003e\r\n    \u003cartifactId\u003ejjwt\u003c/artifactId\u003e\r\n    \u003cversion\u003e0.9.1\u003c/version\u003e\r\n\u003c/dependency\u003e\r\n\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003ejavax.xml.bind\u003c/groupId\u003e\r\n    \u003cartifactId\u003ejaxb-api\u003c/artifactId\u003e\r\n    \u003cversion\u003e2.3.1\u003c/version\u003e\r\n\u003c/dependency\u003e\r\n```\r\n\r\n`UsernamePasswordAuthenticationFilter` is the dafault class Spring uses.\r\nBy extending from it we can override some methods and give them specific implementation.\r\n\r\n### First step: validates credentials\r\n- Modelate authentication request with `UsernameAndPasswordAuthenticationRequest.class`\r\n- Override `attempAuthentication` method.\r\n- If username and password are correct, this method return the Authentication\r\n\r\n### Second step: generates token and send it to the client\r\n- `successfulAuthentication` method will be executed only if `attempAuthentication` does not fail.\r\n- Create the token\r\n- Send it to the client\r\n\r\n![jwt_steps](https://github.com/amadr-95/spring-security/assets/122611230/d1d0017c-53a1-4d83-9b71-990162c8839d)\r\n\r\nOnce implemented, we can access to `http://localhost:8080/api/v1/auth/authenticate` and try to log in with any user:\r\n\r\n![token](https://github.com/amadr-95/spring-security/assets/122611230/bc3238b1-4646-4102-b49f-5c30af54c4bf)\r\n\r\nIf credentials are correct, a token will be generated. This token must be used as authorization method in every request to the API as follows:\r\n\r\n![request_token](https://github.com/amadr-95/spring-security/assets/122611230/da4b2057-622c-4c11-b43a-55c79d49ea13)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famadr-95%2Fspring-security","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famadr-95%2Fspring-security","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famadr-95%2Fspring-security/lists"}