はじめに
アサインされたプロジェクのJWT×REST API認証方法が少し特殊で、お客さんからなんでspring-security使ってないのと聞かれ焦り...
一般的なspring security × JWT × REST APIとはどんなものか理解したく、デモアプリケーションを実装してみました。
環境
- Windows 10 Home
- Java 1.8.0_221
- Spring Boot 2.5.1
- Spring Security 5.5.0
- jjwt 0.11.2
- H2 1.4.200
- Spring DATA JPA 2.5.1
参考
- Spring Security Reference
- Hello Spring Security with Boot
- Spring Security & JWT with Spring Boot 2.0で簡単なRest APIを実装する
- JWTによるREST APIのログインを実現する
- [JWTトークンについて]
(https://hiyosi.tumblr.com/post/70073770678/jwt)
ソース
https://github.com/Takeuchi713/spring-security-jwt-sample
デモアプリケーションの概要
メールアドレスとパスワードでユーザーを認証し、tokenを発行します。
発行されたtokenはヘッダーで返し、以降の認証・許可はリクエストヘッダーにセットしたtokenによって行います。
権限について
"USER","ADMIN"があり、権限の範囲内のAPIしか呼び出すことができません。
具体的にはADMIN権限を持つユーザーは全データへのアクセス権があり。
USER権限のユーザーは自身のデータへのアクセス権しかない状態とします。
イメージ図
実装するAPI
メソッド | パス | 権限 | 備考 |
---|---|---|---|
GET | /api/vi/public/hello | なし | 未ログインで呼び出し可能 |
GET | /api/vi/admin/find/{id} | ADMIN | 利用者情報の取得 |
GET | /api/vi/admin/find/all | ADMIN | 全利用者情報の取得 |
POST | /api/v1/admin/add | ADMIN | 利用者情報の登録 |
PUT | /api/v1/admin/update | ADMIN | 利用者情報の変更 |
DELETE | /api/v1/admin/delete/{id} | ADMIN | 利用者情報の削除 |
GET | /api/v1/user/find/me | USER | 自身の情報取得 |
PUT | /api/v1/user/update/me | USER | 自身の情報変更 |
DELETE | /api/v1/user/delete/me | USER | 自身の情報削除 |
DB
インメモリDBのH2を使用しています。
アプリケーション起動時にテーブルが自動的に作成。初期データがinsertされます。
今回はサンプルで初期データとしてUser権限、Admin権限、両権限を持つユーザーをINSERTしています。
また、各userのpasswordは12345をBCryptで暗号化した値です。
--Userテーブル作成
CREATE TABLE user (
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
roles VARCHAR(100) NOT NULL,
password VARCHAR(150) NOT NULL,
is_active BOOLEAN NOT NULL
);
--INSERT Role USER
INSERT INTO USER(name,email,roles,password,is_active) VALUES(
'user','test@test.com','ROLE_USER','$2a$10$27Kls5TCTZUttJHzlmuUqecS0Ab7jRFp2vmBMqKk3HeW3p3ebFx4m',true
);
--INSERT Role ADMIN
INSERT INTO USER(name,email,roles,password,is_active) VALUES(
'admin','test2@test.com','ROLE_ADMIN','$2a$10$27Kls5TCTZUttJHzlmuUqecS0Ab7jRFp2vmBMqKk3HeW3p3ebFx4m',true
);
--INSERT Role ADMIN and USER
INSERT INTO USER(name,email,roles,password,is_active) VALUES(
'admin_user','test3@test.com','ROLE_USER,ROLE_ADMIN','$2a$10$27Kls5TCTZUttJHzlmuUqecS0Ab7jRFp2vmBMqKk3HeW3p3ebFx4m',true
);
起動後のh2-console
実装
#1. 元となるアプリの作成
まずはUserのCRUD処理を作成する。
ADMIN権限とUSER権限用の二種類のコントローラーとAPIを作成。
1-0. build.gradle
plugins {
id 'org.springframework.boot' version '2.5.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.takeuchi'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
//swaggerでAPIを一覧に出したかったので追加。趣旨とはあまり関係ない。
implementation 'io.springfox:springfox-swagger2:2.8.0'
implementation 'io.springfox:springfox-swagger-ui:2.8.0'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
1-1. Entity
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name", length = 100, nullable = false)
private String name;
@Column(name = "email", length = 100, nullable = false, unique = true)
private String email;
@Column(name = "roles", length = 100, nullable = false)
private String roles;
@Column(name = "password", length = 150, nullable = false)
private String password;
@Column(name = "is_active", nullable = false)
private Boolean active;
}
1-2. Controller
@RestController
@RequestMapping(CommonConstants.API_BASE_PATH + "/admin")
public class AdminUserController {
private final UserService userService;
@Autowired
AdminUserController(UserService userService) {
this.userService = userService;
}
//idからUser情報を取得
@GetMapping("/find/{id}")
public ResponseEntity<User> findById(@PathVariable(name="id", required = true) Integer id){
User user = userService.findById(id);
HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<User>(user, headers, HttpStatus.OK);
}
//全てのUser情報を取得
@GetMapping("/find/all")
public ResponseEntity<List<User>> findAll(){
List<User> users = userService.findAll();
HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<List<User>>(users, headers, HttpStatus.OK);
}
//Userを作成
@PostMapping("/add")
public ResponseEntity<User> addUser(@RequestBody @Valid UserRequest userRequest) {
User user = userService.addUser(userRequest);
HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<User>(user, headers, HttpStatus.CREATED);
}
//Userを更新
@PutMapping("/update")
public ResponseEntity<String> updateUser(@RequestBody @Valid UserRequest userRequest) {
userService.updateUser(userRequest);
HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<String>("update successed", headers, HttpStatus.OK);
}
//Userを削除
@DeleteMapping("/delete/{id}")
public ResponseEntity<String> deleteUserById(@PathVariable(name = "id", required = true) Integer id) {
userService.deleteUserById(id);
HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<String>("user id: " + id + " was deleted", headers, HttpStatus.OK);
}
}
@RestController
@RequestMapping(CommonConstants.API_BASE_PATH + "/user")
public class UserController {
private final UserService userService;
@Autowired
UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/find/me")
public ResponseEntity<User> findMe(){
User user = userService.findMe();
HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<User>(user, headers, HttpStatus.OK);
}
@PutMapping("/update/me")
public ResponseEntity<User> updateMe(HttpServletRequest request, @RequestBody @Valid UserRequest userRequest){
User user = userService.updateMe(userRequest);
HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<User>(user, headers, HttpStatus.OK);
}
@DeleteMapping("/delete/me")
public ResponseEntity<String> deleteMe(HttpServletRequest request){
userService.deleteMe();
HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<String>("delete successed", headers, HttpStatus.OK);
}
}
@RestController
@RequestMapping(CommonConstants.API_BASE_PATH + "/public")
public class PublicController {
@GetMapping("/hello")
public ResponseEntity<String> hello() {
return new ResponseEntity<String>("hello", new HttpHeaders(), HttpStatus.OK);
}
}
1-3. Service
@Service
public class UserServiceImp implements UserService {
private final UserRepository userRepository;
private final HttpServletRequest servletRequest;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
UserServiceImp(UserRepository userRepository, HttpServletRequest servletRequest,
BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userRepository = userRepository;
this.servletRequest = servletRequest;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Override
@Transactional(readOnly = true)
public User findMe() {
// filterで追加したidを取得
Integer id = getId();
if(id == null) {
throw new UserNotFoundException("no id");
}
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("user with id: " + id + "was not found"));
}
@Override
@Transactional
public User updateMe(UserRequest userRequest) {
Integer id = getId();
if(id == null) {
throw new UserNotFoundException("no id");
}
User updateUser = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("\"user with id: \" + id + \"was not found\""));
updateUser.setEmail(userRequest.getEmail());
updateUser.setName(userRequest.getName());
updateUser.setRoles(userRequest.getRoles());
updateUser.setPassword(bCryptPasswordEncoder.encode(userRequest.getPassword()));
updateUser.setActive(userRequest.getIs_active());
userRepository.save(updateUser);
return updateUser;
}
@Override
@Transactional
public void deleteMe() {
Integer id = getId();
if (id == null || userRepository.findById(id).get() == null) {
throw new UserNotFoundException("no user was found");
}
userRepository.deleteById(id);
}
@Override
@Transactional(readOnly = true)
public User findById(Integer id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("user with id: " + id + "was not found"));
return user;
}
@Override
@Transactional(readOnly = true)
public List<User> findAll() {
List<User> users = userRepository.findAll();
return users;
}
@Override
@Transactional
public User addUser(UserRequest userRequest) {
User newUser = new User();
newUser.setName(userRequest.getName());
newUser.setEmail(userRequest.getEmail());
newUser.setRoles(userRequest.getRoles());
newUser.setPassword(bCryptPasswordEncoder.encode(userRequest.getPassword()));
newUser.setActive(userRequest.getIs_active());
User user = userRepository.save(newUser);
return user;
}
@Override
@Transactional
public void updateUser(UserRequest userRequest) {
if(userRequest.getId() == null) {
throw new ValidationException("id must not be null");
}
User updateUser = userRepository.findById(userRequest.getId())
.orElseThrow(() -> new UserNotFoundException("user by id: " + userRequest.getId() + " was not found"));
updateUser.setEmail(userRequest.getEmail());
updateUser.setName(userRequest.getName());
updateUser.setRoles(userRequest.getRoles());
updateUser.setPassword(bCryptPasswordEncoder.encode(userRequest.getPassword()));
updateUser.setActive(userRequest.getIs_active());
userRepository.save(updateUser);
}
@Override
@Transactional
public void deleteUserById(Integer id) {
if(id == null) {
throw new ValidationException("id must not be null");
}
userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("user by id: " + id + " was not found"));
userRepository.deleteById(id);;
}
private Integer getId() {
Integer id = null;
try {
String idStr = (String) servletRequest.getAttribute(CommonConstants.USER_ID);
id = Integer.valueOf(idStr);
}catch(Exception e) {
return id;
}
return id;
}
}
1-4. 動作確認
rem 全ユーザーを取得
>curl http://localhost:8080/api/v1/admin/find/all
[{"id":1,"name":"user","email":"test@test.com","roles":"ROLE_USER","password":"$2a$10$27Kls5TCTZUttJHzlmuUqecS0Ab7jRFp2vmBMqKk3HeW3p3ebFx4m","active":true},
{"id":2,"name":"admin","email":"test2@test.com","roles":"ROLE_ADMIN","password":"$2a$10$27Kls5TCTZUttJHzlmuUqecS0Ab7jRFp2vmBMqKk3HeW3p3ebFx4m","active":true},
{"id":3,"name":"admin_user","email":"test3@test.com","roles":"ROLE_USER,ROLE_ADMIN","password":"$2a$10$27Kls5TCTZUttJHzlmuUqecS0Ab7jRFp2vmBMqKk3HeW3p3ebFx4m","active":true}]
rem ユーザー削除
>curl -X DELETE -H "Content-Type: application/json" http://localhost:8080/api/v1/admin/delete/1
user id: 1 was deleted
{
"id": "",
"name": "test3",
"email": "test5@test.com",
"roles": "ROLE_USER",
"password": "12345",
"is_active": true
}
2. spring securityを追加
1.で作成したアプリにspring securityを追加していく。
jwtのライブラリはjjwtを選択。
https://github.com/jwtk/jjwt
https://jwt.io/
2-1. gradleに依存性を追加
spring-security、jjwtを追加。
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2','io.jsonwebtoken:jjwt-jackson:0.11.2'
//test用
testImplementation 'org.springframework.security:spring-security-test'
2-2. Configurationの設定
WebSecurityConfigurerAdapterを拡張した認証の為のConfigurationクラス。
エンドポイント別にAPIのアクセスを制限する。
エンドポイント | 制限 |
---|---|
/public/** | 誰でもアクセス可能 |
/user/** | USER_ROLE、ADMIN_ROLEのみ |
/admin/** | ADMIN_ROLEのみ |
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
//swaggerが認証エラーとならない為の設定
.antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//CSRFを許可
.csrf().disable()
//REST想定なのでセッションをステートレスに
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//リクエスト認証
.authorizeRequests()
//tokenなしでも許容するAPI
.mvcMatchers(CommonConstants.API_BASE_PATH + "/public/**", "/swagger-ui.html/**","/h2-console/**")
.permitAll()
//ROLE_USER or ROLE_ADMIN権限が必要なAPI
.mvcMatchers(CommonConstants.API_BASE_PATH + "/user/**")
.hasAnyRole("USER","ADNMIN")
//ROLE_ADMIN権限が必要なAPI
.mvcMatchers(CommonConstants.API_BASE_PATH + "/admin/**")
.hasRole("ADMIN")
.anyRequest().authenticated()
.and()
//request認証とtokenを発行するfilter
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
//tokenの承認を行うfilter
.addFilterAfter(new JWTAuthorizationFilter(), JWTAuthenticationFilter.class)
//エラーハンドリング
.exceptionHandling()
// ログインエラー時のハンドラー設定(未ログインも)
.accessDeniedHandler(new JWTAccessDeniedHandler())
// 権限エラー時のハンドラー設定
.authenticationEntryPoint(new JWTAuthenticationEntryPoint())
.and()
//h2-consoleへ接続するための設定
.headers().frameOptions().disable();
}
@Bean
//passwordのエンコーダー
public BCryptPasswordEncoder bCryptPasswordEncoder () {
return new BCryptPasswordEncoder(10);
}
}
2-3. ログイン認証処理の作成
概要
主要な処理とクラス。
・JWTAuthenticationFilter : Requetを認証しtokenを発行
・UserDetailsServiceImp : DBとrequestが一致するか判定
・LoginUser : ユーザ情報をspring-securityで使用できる形へ変換
処理イメージ
点線の箇所はspring-securityがいい感じで裏で呼び出してくれる。
JWTAuthenticationFilter
request認証とtokenを発行するFilter。
"/api/v1/login"へPOSTするとここを通過する。
@Slf4j
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
//デフォルトのpathを変更
setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(CommonConstants.LOGIN_URL, "POST"));
//デフォルトのパラメーターを変更
setUsernameParameter(CommonConstants.EMAIL);
setPasswordParameter(CommonConstants.PASSWORD);
}
@Override
//ログイン認証処理
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
try {
LoginRequest loginRequest = new ObjectMapper().readValue(request.getInputStream(), LoginRequest.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(), //principal
loginRequest.getPassword() //credencial
);
return authenticationManager.authenticate(authentication);
} catch (Exception e) {
log.warn(e.getMessage());
throw new RuntimeException(e);
}
}
@Override
//ログイン認証成功後の処理
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
//token発行
String token = Jwts.builder()
.setSubject(((LoginUser)authResult.getPrincipal()).getUser().getId().toString()) //id
.claim(CommonConstants.AUTHORITES, authResult.getAuthorities()) //権限
.setIssuedAt(new Date()) //発行日
.setExpiration(new Date(System.currentTimeMillis() + 10 * 60 * 1000)) //有効期限
.signWith(Keys.hmacShaKeyFor(CommonConstants.SECRET_KEY.getBytes())) //暗号化key
.compact();
response.addHeader(CommonConstants.AUTHORIZED_HEADER, CommonConstants.TOKEN_PREFIX + token);
}
}
UserDetailsServiceImp
@Service
@Slf4j
public class UserDetailsServiceImp implements UserDetailsService {
private final UserRepository userRepository;
@Autowired
UserDetailsServiceImp(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
log.info("user {} was accessed", email);
return userRepository.findByEmail(email)
.map(LoginUser::new)
.orElseThrow(() -> new UsernameNotFoundException("User [" + email + "] was not found"));
}
}
LoginUser
//EntityのUserとUserDetailをマッピングさせるクラス
public class LoginUser extends org.springframework.security.core.userdetails.User {
//Entity
@Getter
private final User user;
public LoginUser(User user) {
super(user.getName(), user.getPassword(), user.getActive(),
true, true, true, convertGrandtedAuthorites(user.getRoles()));
this.user= user;
}
//DBのrolesからGrantedAuthorityへ変換する
private static Set<GrantedAuthority> convertGrandtedAuthorites(String roles){
if (!StringUtils.hasText(roles)) {
return Collections.emptySet();
}
Set<GrantedAuthority> authorites = Stream.of(roles.split(","))
.map(String::trim)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
return authorites;
}
}
2-4. token承認filterの作成
@Slf4j
public class JWTAuthorizationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String header = request.getHeader(CommonConstants.AUTHORIZED_HEADER);
if(!StringUtils.hasText(header) || !header.startsWith(CommonConstants.TOKEN_PREFIX) ) {
log.info("request {} was called by token {}", request.getRequestURI(), header);
filterChain.doFilter(request, response);
return;
}
String token = header.replace(CommonConstants.TOKEN_PREFIX, "");
if(token != null) {
try {
//tokenを復号し中身を取得。不正があるとJwtExceptionが発生する。
Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(CommonConstants.SECRET_KEY.getBytes()))
.build()
.parseClaimsJws(token);
//復号tokenから認証情報を作成する。
Authentication authentication = getAuthentication(claimsJws);
SecurityContextHolder.getContext().setAuthentication(authentication);
//ユーザidを追加
if(claimsJws.getBody().getSubject() != null) {
request.setAttribute(CommonConstants.USER_ID, claimsJws.getBody().getSubject());
}
filterChain.doFilter(request, response);
}catch(JwtException je) {
log.error(je.getMessage());
throw new IllegalStateException(String.format("Token %s cannnot be trusted ", token));
}catch(Exception e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
}
}
private Authentication getAuthentication(Jws<Claims> claimsJws) {
//user_id
String userId = claimsJws.getBody().getSubject();
//GrantedAuthorites
@SuppressWarnings("unchecked")
List<Map<String, String>> authorites = (List<Map<String, String>>) claimsJws.getBody()
.get(CommonConstants.AUTHORITES);
Set<GrantedAuthority> grantedAuthorities = authorites.stream()
.map(k -> new SimpleGrantedAuthority(k.get("authority")))
.collect(Collectors.toSet());
return new UsernamePasswordAuthenticationToken(userId, null, grantedAuthorities);
}
}
2-5. エラーハンドリング
レスポンスbodyにエラー情報を載せるカスタムクラスを作成。
JWTAuthenticationEntryPoint : ログインエラー、未ログインでAPIアクセス時のハンドラー
JWTAccessDeniedHandler : 権限エラー時のハンドラー
@Slf4j
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
log.error(authException.getMessage());
ErrorResponse er = ErrorResponse.builder()
.status(HttpStatus.UNAUTHORIZED.value())
.error(HttpStatus.UNAUTHORIZED.getReasonPhrase())
.message(authException.getMessage())
.path(request.getServletPath())
.build();
ObjectMapper om = new ObjectMapper();
String json = er.toJson(om);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.getWriter().write(json);
}
}
@Slf4j
public class JWTAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.error(accessDeniedException.getMessage());
ErrorResponse er = ErrorResponse.builder()
.status(HttpStatus.FORBIDDEN.value())
.error(HttpStatus.FORBIDDEN.getReasonPhrase())
.message(accessDeniedException.getMessage())
.path(request.getServletPath())
.build();
ObjectMapper om = new ObjectMapper();
String json = er.toJson(om);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.getWriter().write(json);
}
}
3. 動作確認
tokenなしで呼べるAPI
>curl -i -X GET http://localhost:8080/api/v1/public/hello
HTTP/1.1 200
hello
token
user権限
{
"sub": "1",
"authorites": [
{
"authority": "ROLE_USER"
}
],
"iat": 1627136424,
"exp": 1627137024
}
admin権限
{
"sub": "2",
"authorites": [
{
"authority": "ROLE_ADMIN"
}
],
"iat": 1627136448,
"exp": 1627137048
}
両方の権限
{
"sub": "3",
"authorites": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"iat": 1627136050,
"exp": 1627136650
}
login成功
>curl -i -X POST -H "Content-Type: application/json" -d "{\"email\":\"test3@test.com\",\"password\":\"12345\"}" http://localhost:8080/api/v1/login
HTTP/1.1 200
X-Authenticated-token: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIzIiwiYXV0aG9yaXRlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9LHsiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn1dLCJpYXQiOjE2MjcxMzQ4NTIsImV4cCI6MTYyNzEzNTQ1Mn0.inu8MZF4Qt2o547NsKCIaGsNDucwb211KQ0F4fkgeK02hiSccoKQeE1O3cnIJJz79joFIbsYAR5FQV04e15t9w
API実行
rem API:find/me (user権限)
>curl -i -X GET -H "Content-Type: application/json" -H "X-Authenticated-token: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiYXV0aG9yaXRlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn1dLCJpYXQiOjE2MjcxMzUyMDAsImV4cCI6MTYyNzEzNTgwMH0.xhVVmd9gNXDwjEnfi2Ho0mRNp79s-zUIzgl5IY9A53OmT7uJPAqcz1cCd0lwmm6aPxAGymGjmvVT-C6Uv3B-0w" http://localhost:8080/api/v1/user/find/me
HTTP/1.1 200
{"id":1,"name":"user","email":"test@test.com","roles":"ROLE_USER","password":"$2a$10$27Kls5TCTZUttJHzlmuUqecS0Ab7jRFp2vmBMqKk3HeW3p3ebFx4m","active":true}
rem API:add (admin権限)
>curl -i -X POST -H "Content-Type: application/json" -H "X-Authenticated-token: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIzIiwiYXV0aG9yaXRlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9LHsiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn1dLCJpYXQiOjE2MjcxMzYwNTAsImV4cCI6MTYyNzEzNjY1MH0.mZyYMH3r7Hxtf7QCEQmTpsqr9XqXgDN5uRBE-0ZgNdl2Itp3aBscU6SGZRSIrVc8wz_86CayHgkBiP1RihA-vA" -d @userjson.txt http://localhost:8080/api/v1/admin/add
HTTP/1.1 201
{"id":4,"name":"test3","email":"test5@test.com","roles":"ROLE_USER","password":"$2a$10$YynA0I5cd8mAYJ9aorl4f.iaxg9RghCCW29ivzxuke5PWj0Y/BDci","active":true}
login失敗
>curl -i -X POST -H "Content-Type: application/json" -d "{\"email\":\"test3@test.com\",\"password\":\"aaaa\"}" http://localhost:8080/api/v1/login
HTTP/1.1 401
{"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/error"}
token不足
>curl -i -X POST -H "Content-Type: application/json" -d @userjson.txt http://localhost:8080/api/v1/admin/add
HTTP/1.1 401
{"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/api/v1/admin/add"}
USER権限でADMIN権限のAPIを実行
>curl -i -X GET -H "X-Authenticated-token: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiYXV0aG9yaXRlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn1dLCJpYXQiOjE2MjcxMzY1ODYsImV4cCI6MTYyNzEzNzE4Nn0.QTfRhnWBSIhEYxFLLoL_1-gphWBuMnD6uI5jOySS2QAYzhJdgcb99VIjq_VxAYFTKngAQphgv9eaJNF3FmroTA" http://localhost:8080/api/v1/admin/find/all
HTTP/1.1 403
{"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/v1/admin/find/all"}