- 03-5820-1777平日10:00〜18:00
- お問い合わせ
Vue.js、Spring Bootシステムでの認証方法の続きで今回は認証で取得した権限毎にアクセス可能な画面を用意し、アクセス制御してみたいと思います。
前回作成した物に追加、修正を加えて行きたいと思います。
処理の流れ
作成モジュール一覧
Vue.js、Spring Bootシステムでの認証方法からのVue.js部分の追加モジュールです。
今回Spring Boot部分の追加部分はありません。
モジュール名 | 追加・修正 | 説明 |
---|---|---|
App.vue | 追加 | ヘッダーを既存のApp.vueに入れる このvueに<router-view/>タグを入れてVue Routerで遷移した画面が表示される。 |
router/index.js | 追加 | Vue Routerを使用しての画面遷移部分の記述、URLと権限の関係をチェックし、問題があればAuthorityError.vueへ遷移する処理を記述する |
GoodsList.vue | 追加 | 権限管理の動きを確認する画面が必要なので、何の画面でも良いですが、今回は商品リスト画面を用意します |
GoodsRepository.js | 追加 | 商品情報をサーバから取得するモジュール |
AuthorityError.vue | 追加 | 権限エラーを検知した際に遷移する画面です |
AccountRepository | 修正 | ログアウト部分を追加する |
実装
・App.vue
画面のstyleは見やすいように定義しています。
<template>
<div>
<header class="header">
<div class="headerNav">
<nav>
<!--ログイン画面の場合はメニューを表示しない -->
<ul v-show="$route.path !== '/'" class="b">
<!-- Vuexのロール情報を取得し、adminの場合のみユーザ一覧へのリンクを表示 -->
<li v-show="store.getters.getRole=='admin'"><a href="#" @click.prevent.stop="trasPage('accountList')">ユーザ一覧</a></li>
<!-- 商品一覧とログアウトは全てのユーザが閲覧可能 -->
<li><a href="#" @click.prevent.stop="trasPage('goodsList')">商品一覧</a></li>
<li><a href="#" @click.prevent.stop="logout">ログアウト</a></li>
</ul>
</nav>
</div>
</header>
<!--Vue Router用のタグ、URLに対応した画面を表示 -->
<router-view/>
</div>
</template>
<script>
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import accountRepository from './composables/AccountRepository'
export default ({
name: 'App',
setup(){
const router = useRouter()
const store = useStore()
const { logoutAccount } = accountRepository()
//メニューのリンクが押下された時に呼ばれる
//router.pushメソッドで画面遷移依頼
const trasPage = ( page ) => {
if( page == "goodsList"){
router.push("/goodsList")
}else if( page == "accountList"){
router.push("/accountList")
}
}
//ログアウト処理用のコールバック
const callback = ()=>{
router.push("/")
}
//ログアウト押下時の処理
const logout = ()=>{
logoutAccount( callback )
}
return{
trasPage,
store
}
}
})
</script>
<style>
.header{
display: flex;
width: 100%;
height:90px;
padding: 0%;
justify-content: space-between;
background-color:#ff69b4;
maring: 0px 0%;
}
.b{
display: flex;
font-size: 18px;
text-transform: uppercase;
margin-top: 30px;
margin-right: 30px;
list-style: none;
}
.b li{
margin-left: 30px;
}
.b li a{
color: #0000ff;
text-decoration: none;
}
.headerNav{
display: flex;
width: 100%;
height:90px;
padding: 0%;
justify-content: space-between;
maring:0%;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
・router/index.js
import { createRouter, createWebHistory } from 'vue-router'
//ログイン、ユーザ一覧、商品一覧、権限エラーの4つの画面
//の遷移を管理する
import Login from '../views/Login.vue'
import AccountList from '../views/AccountList.vue'
import GoodsList from '../views/GoodsList.vue'
import AuthorityError from '../views/AuthorityError.vue'
import store from '@/store/index';
const routes = [
{
path: '/',
name: 'login',
component: Login
},
{
path: '/accountList',
name: 'accountList',
component: AccountList
},
{
path: '/goodsList',
name: 'goodsList',
component: GoodsList
},
{
path: '/authorityError',
name: 'authorityError',
component: AuthorityError
},
]
//historyモードでルーター作成
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});
//画面遷移が行われる前に呼ばれるメソッド
//ここでURL直接入力された時などのために、権限チェックを行う
router.beforeEach((to, from, next) => {
//パスがログインの場合はそのまま遷移
if( to.path == "/"){
next()
return
}
//非ログイン状態の時はログイン画面へ遷移
if( !store.getters.getIsLogin ){
router.push("/")
}else{
//ログイン状態の時
//権限がguestでユーザ一覧画面へ遷移しようとしてる場合は権限エラー画面に遷移
//それ以外の時はそのまま遷移
var role = store.getters.getRole
if( role == "guest" && ( to.path == "/accountList" ) ){
router.push("/authorityError")
}else{
next()
}
}
})
export default router
router.beforeEachメソッドは他の箇所に実装する事も可能です。例を下に記述します。
<template>
省略...
</template>
<script>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
export default ({
name: 'App',
setup(){
const router = useRouter()
router.beforeEach((to, from, next) => {
//パスがログインの場合はそのまま遷移
if( to.path == "/"){
next()
return
}
//非ログイン状態の時はログイン画面へ遷移
if( !store.getters.getIsLogin ){
router.push("/")
}else{
//ログイン状態の時
//権限がguestでユーザ一覧画面へ遷移しようとしてる場合は権限エラー画面に遷移
//それ以外の時はそのまま遷移
var role = store.getters.getRole
if( role == "guest" && ( to.path == "/accountList" ) ){
router.push("/authorityError")
}else{
next()
}
}
})
}
})
・GoodsList.vue
<template>
<div>
<div class="inputItemDiv"><div style="color:red;">{{message}}</div></div>
<div class="table" style="max-height:300px;width:900px;">
<table>
<thead>
<tr>
<th>コード</th>
<th>名前</th>
<th>カテゴリ</th>
<th>価格</th>
</tr>
</thead>
<tbody>
<tr v-for="(data) in goodsList" :key="data.id">
<td>{{ data.code }}</td>
<td>{{ data.name }}</td>
<td>{{ data.category }}</td>
<td>{{ data.price }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import goodsRepository from '../composables/GoodsRepository'
export default ({
name: 'GoodsList',
setup(){
//商品情報リスト
const goodsList = ref([])
//商品取得結果メッセージ
const message = ref(null)
//goodsRepositoryモジュールからgetGoodsメソッドを取得
const { getGoods } = goodsRepository()
//データ取得時のコールバック( GoodsRepositoryのgetGoodsメソッドに渡す)
const callback = ( result, data )=>{
//取得成功の場合はgoodsListに取得データを設定
if( result == "success"){
message.value = "データ取得完了"
goodsList.value = data
}else{
message.value = result
}
}
//ユーザ情報取得
getGoods( callback )
return{
goodsList,
message
}
}
})
</script>
・GoodsRepository
import store from '@/store/index';
import * as axios from 'axios'
export default function (){
//商品情報取得
const getGoods = async( callback ) => {
try{
//Spring Bootにaxiosで商品取得依頼
var token = store.getters.getAuthToken
console.log( token )
var goodsList = await requestServerGetWidthToken ( "api/goods/list", token)
//コールバックに結果を返す
callback( "success", goodsList )
}catch(e){
callback(e)
}
}
//axiosのGetメソッドでトークンをヘッダーにセットしSpring Boot側へリクエストを掛ける
const requestServerGetWidthToken = ( url, token ) =>{
return new Promise((resolve, reject) => {
axios
.get('http://localhost:8080/' + url
,{
headers: {
"X-AUTH-TOKEN" : token
}
}
)
.then(response => {
resolve(response.data)
}).catch(error => {
reject(error)
})
}).catch((e) => {
throw e
})
}
return{
getGoods
}
}
・AuthorityError.vue
<template>
<div class="contentMainDiv">
<div style="color:red;">このページにアクセスする権限がありません</div>
</div>
</template>
・AccountRepository
ログアウト機能を追加します。(追加部分のみ追記)
const logoutAccount = async( callback ) => {
try{
//サーバにログアウト依頼
await requestServerPost("logout")
//ログイン、権限、トークン情報を初期化
store.commit("setIsLogin", false)
store.commit("setRole", "")
store.commit("setAuthToken", "")
callback("success")
}catch(e){
callback(e)
}
}
return{
loginAccount,
getAccount,
logoutAccount
}
以上がVue.js側の実装になります。
前回のクラスの修正と今回分の追加を合わせて記述します。
修正点は権限管理部分になります。RestController側で権限チェックをし、エラーなら例外を返す形となります。
修正・作成クラス一覧
クラス名 | 追加・修正 | 説明 |
---|---|---|
AccountController | 修正 | 権限チェックをし、エラーなら例外を返すように修正する |
GoodsController | 追加 | 商品情報取得用のRestController 商品情報一覧画面から呼ばれる。ここは全ユーザがアクセス可能な為権限 チェックを行わない |
GoodsRepository | 追加 | DBのGoodsテーブルへクエリーを掛けるクラス |
Goods | 追加 | DBのGoodsテーブルに対応するエンティティクラス |
RoleAuthorityException | 追加 | 権限エラーの際に投げる例外クラス |
実装
・AccountController
此方はadmin権限のみ閲覧可能なので、メソッド内で権限チェックをする処理を追加する。
package com.example.authsample.ac;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.example.authsample.entity.Account;
import com.example.authsample.repository.AccountRepository;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/api/account")
@CrossOrigin
public class AccountController {
@Autowired
private AccountRepository accountRepository;
/**
* ユーザリストを取得
* @return
*/
@RequestMapping(value = "/list", method = RequestMethod.GET)
public Flux<Account> getUserList() throws Exception{
//権限がadmin以外からの受付を拒否する処理
//セッションから権限情報を取得
String role = ( String )SecurityContextHolder.getContext().getAuthentication().getAuthorities().iterator().next().getAuthority();
//ダブルクォート削除処理
role = role.replaceAll("\"", "");
// 権限がadminでない場合は例外を発生
if( !role.equals("admin")) {
throw new RoleAuthorityException("アクセス権限がありません。");
}
return this.accountRepository.findAll();
}
}
・GoodsController
此方は全ユーザで閲覧可能なのでチェック無し。
package com.example.authsample.ac;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.example.authsample.entity.Goods;
import com.example.authsample.repository.GoodsRepository;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/api/goods")
@CrossOrigin
public class GoodsController {
@Autowired
private GoodsRepository goodsRepository;
/**
* 商品リストを取得
* @return
*/
@RequestMapping(value = "/list", method = RequestMethod.GET)
public Flux<Goods> getUserList() throws Exception{
return this.goodsRepository.findAll();
}
}
・GoodsRepository
package com.example.authsample.repository;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import com.example.authsample.entity.Goods;
public interface GoodsRepository extends ReactiveCrudRepository<Goods, Integer> {
}
・Goods
package com.example.authsample.entity;
import java.math.BigDecimal;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table("goods")
public class Goods {
@Id
private Long id;
private String code;
private String name;
private String category;
private BigDecimal price;
}
以上で権限のチェック処理を追加した例です。
他にも色々方法はあると思いますが、今私がやっている方法を紹介しました。
今回の認証チェック、権限チェックのサンプルは此方からダウンロード可能です。