{"id":15745222,"url":"https://github.com/gabrielbb/springboot-jwt-starter","last_synced_at":"2025-10-08T00:25:25.511Z","repository":{"id":218249193,"uuid":"125308303","full_name":"GabrielBB/SpringBoot-JWT-Starter","owner":"GabrielBB","description":"Spring Boot starter project with JSON Web Token Authentication and Authorization enabled","archived":false,"fork":false,"pushed_at":"2019-08-27T13:22:20.000Z","size":136,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-06T10:18:58.549Z","etag":null,"topics":[],"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/GabrielBB.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}},"created_at":"2018-03-15T03:38:55.000Z","updated_at":"2023-10-22T11:57:58.000Z","dependencies_parsed_at":"2024-01-20T18:45:45.264Z","dependency_job_id":"1539ee28-ecee-401f-80ab-f888f9312146","html_url":"https://github.com/GabrielBB/SpringBoot-JWT-Starter","commit_stats":null,"previous_names":["gabrielbb/springboot-jwt-starter"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GabrielBB%2FSpringBoot-JWT-Starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GabrielBB%2FSpringBoot-JWT-Starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GabrielBB%2FSpringBoot-JWT-Starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GabrielBB%2FSpringBoot-JWT-Starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GabrielBB","download_url":"https://codeload.github.com/GabrielBB/SpringBoot-JWT-Starter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246423494,"owners_count":20774796,"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-04T04:02:26.330Z","updated_at":"2025-10-08T00:25:20.475Z","avatar_url":"https://github.com/GabrielBB.png","language":"Java","readme":"# SpringBoot-JWT-Starter\n\nSpring Boot starter project with JSON Web Token Authentication and Authorization enabled\n\n## The User Entity\n\nWe have a model representing an user and his role. Role can also be another model class, but this project tries to keep things simple so is easy for you to make your modifications or additions. This entity will travel in every authenticated request decoded as a token in JSON format. For security, the **password** field will be the only one excluded when the token is generated because it is only used to validate credentials with an in-memory database. Once the user is authenticated, this property is no longer needed. \n\n```java\npackage com.github.gabrielbb.models;\n\n@Entity\npublic class User {\n\n    @Id\n    private Integer id;\n    private String name;\n    private String password;\n    private String role;\n}\n```\n\n## The User Repository\n\nUsing [Spring Data JPA](https://spring.io/guides/gs/accessing-data-jpa/) to create an User Repository so we can fetch an user from the in-memory database based on username and password provided (we are using [H2 database](http://www.springboottutorial.com/spring-boot-and-h2-in-memory-database) but you can switch the project to use a traditional relational database) .\n\n```java\n    package  com.github.gabrielbb.repos;\n   \n    public  interface  UserRepository  extends  CrudRepository\u003cUser, Integer\u003e {\n    \tUser  findByNameAndPassword(String  name, String  password);\n    }\n```\n\n## Token Encoding/Decoding\n\nTo generate a JSON Web Token based on the entity and convert it back to a [POJO](https://en.wikipedia.org/wiki/Plain_old_Java_object), this project is using this utility class i made: [JWTUtility](https://github.com/GabrielBB/jwt-java-utility), it's just an implementation of the library [JJWT](https://github.com/jwtk/jjwt). \n\n## Authentication Controller\n\nWe have an authentication controller with a single \"/login\" method that accepts POST requests. Here we are using the [JWTUtility](https://github.com/GabrielBB/SpringBoot-JWT-Starter/blob/master/src/main/java/com/github/gabrielbb/util/JWTUtility.java) and the UserRepository. The logic here is self explanatory: Check if there is a user registered with that username and password, take the returned entity from the database and generate a token from it. If this is successfully done it returns the HTTP code: [200](https://httpstatuses.com/200) and the token in the response body, otherwise it returns the code [401](https://httpstatuses.com/401).\n\n```java\npackage com.github.gabrielbb.controllers;\n\n@RestController\npublic class AuthController {\n\n    @Autowired\n    private JWTUtility jwtUtil;\n\n    @Autowired\n    private UserRepository userRepo;\n\n    @PostMapping(value = \"/login\")\n    public ResponseEntity\u003cString\u003e login(String name, String password) {\n\n        final User user;\n        if ((user = userRepo.findByNameAndPassword(name, password)) != null) {\n            String jsonWebToken = jwtUtil.getToken(user);\n\n            return ResponseEntity.status(HttpStatus.OK).body(jsonWebToken);\n        }\n\n        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null);\n    }\n}\n```\n\n## Authorization Filter\n\nApart from the login, every single request must be authorized, and for that, the token returned in the login must be saved by the requester and sent in the [header](https://developer.mozilla.org/es/docs/Web/HTTP/Headers) of every request. We intercept the requests, one by one, and check if there is an [Authorization](https://developer.mozilla.org/es/docs/Web/HTTP/Headers/Authorization) header containing the token.\n\nAfter getting the token from the header we need to decode it back to a POJO with a little more help from our JWTUtility. This is how the filter should look by now (i remove imports and some global variable declarations in these examples, full classes are in the project code):\n\n```java\npackage com.github.gabrielbb.config;\n\npublic class JWTAuthorizationFilter extends BasicAuthenticationFilter {\n\n    private final JWTUtility jwtUtil;\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {\n\n        String token = req.getHeader(\"Authorization\");\n\n        if (token != null) {\n            try {\n                User user = jwtUtil.getUser(token);\n\t\t\n            } catch (SignatureException ex) {\n                // User provided an invalid Token\n            }\n        }\n\n        chain.doFilter(req, res);\n    }\n}\n```\n\nNow we have to tell Spring who is this guy and what role he has but Spring doesn't know the user entity and what it is allowed to do. What Spring do understand is an [Authentication](https://docs.spring.io/spring-security/site/docs/4.2.5.BUILD-SNAPSHOT/apidocs/org/springframework/security/core/Authentication.html) object and [Authorities](https://docs.spring.io/spring-security/site/docs/5.0.0.RELEASE/reference/htmlsingle/#tech-granted-authority). Let's traduce our User entity and his role to Spring:\n\n```java\n try {\n                User user = jwtUtil.getUser(token);\n\n                GrantedAuthority roleAuthority = new SimpleGrantedAuthority(user.getRole());\n\n                Authentication authentication = new UsernamePasswordAuthenticationToken(\n                        user.getId(), null, Arrays.asList(roleAuthority));\n\n                SecurityContextHolder.getContext().setAuthentication(authentication);\n            } catch (SignatureException ex) {\n                // User provided an invalid Token\n            }\n```\n\n## Security Maintenance\n\nIn the configuration class **com.github.gabrielbb.config.WebSecurity** we are specifying which routes are open to the world, such as the login, and which routes are protected and the roles that are permitted to request them.\n\n```javascript\nhttp.cors().and().csrf().disable().authorizeRequests()\n                .antMatchers(\"/login\").permitAll()\n                .antMatchers(\"/*\").hasAuthority(\"MASTER_ADMIN\")\n                .antMatchers(\"/cats\").hasAuthority(\"CAT_ADMIN\")\n                .antMatchers(\"/dogs\").hasAuthority(\"DOG_ADMIN\")\n                .and()\n                .addFilter(getAuthorizationFilter())\n                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);\n```\n\n## Unit Testing\n\nSpring automatically reads an SQL file and executes it on your configured database if you place it in: \n\n\u003e /src/main/resources/data.sql\n\nThe **data.sql** file in this project contains:\n\n```sql\ninsert into user values(1, 'bigboss','123', 'MASTER_ADMIN');\ninsert into user values(2, 'SnoopDogg','123', 'DOG_ADMIN');\ninsert into user values(3, 'meow','123', 'CAT_ADMIN');\n```\n\nAnd finally the unit tests validating the HTTP Status Codes every route should return before and after authentication:\n\n```java\npackage com.github.gabrielbb;\n\n@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)\npublic class AuthenticationTests {\n    @Autowired\n    private TestRestTemplate restTemplate;\n\n    @Test\n    public void testControllersWithNoAuth() {\n        HttpStatus catStatus = restTemplate.exchange(\"/cats\", HttpMethod.GET, null, String.class).getStatusCode();\n        HttpStatus dogStatus = restTemplate.exchange(\"/dogs\", HttpMethod.GET, null, String.class).getStatusCode();\n        HttpStatus loginStatus = restTemplate.exchange(\"/login\", HttpMethod.POST, null, String.class).getStatusCode();\n        assertEquals(catStatus, HttpStatus.FORBIDDEN);\n        assertEquals(dogStatus, HttpStatus.FORBIDDEN);\n        assertEquals(loginStatus, HttpStatus.UNAUTHORIZED);\n    }\n\n    @Test\n    public void testSuccessfulLogin() {\n        ResponseEntity\u003cString\u003e response = login(\"SnoopDogg\", \"123\");\n        assertEquals(response.getStatusCode(), HttpStatus.OK);\n    }\n\n    @Test\n    public void testFailedLogin() {\n        ResponseEntity\u003cString\u003e response = login(\"SnoopDogg\", \"Random Password\");\n        assertEquals(response.getStatusCode(), HttpStatus.UNAUTHORIZED);\n    }\n\n    @Test\n    public void testControllersAfterLogin() {\n        ResponseEntity\u003cString\u003e response = login(\"bigboss\", \"123\");\n\n        HttpHeaders headers = new HttpHeaders();\n        headers.set(\"Authorization\", response.getBody()); // Setting JSON Web Token to Request Header\n        HttpEntity\u003c?\u003e entity = new HttpEntity\u003c\u003e(headers);\n\n        HttpStatus catStatus = restTemplate.exchange(\"/cats\", HttpMethod.GET, entity, String.class).getStatusCode();\n        HttpStatus dogStatus = restTemplate.exchange(\"/dogs\", HttpMethod.GET, entity, String.class).getStatusCode();\n        assertEquals(catStatus, HttpStatus.OK);\n        assertEquals(dogStatus, HttpStatus.OK);\n    }\n}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielbb%2Fspringboot-jwt-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgabrielbb%2Fspringboot-jwt-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielbb%2Fspringboot-jwt-starter/lists"}