Admin-Solution ist eine Vorlage für ein Berechtigungsverwaltungssystem.
|
|前端权限控制 --> 动态从后端请求路由
权限控制 --> |
|后端权限控制 --> 进行接口调用访问控制
|
vue-element-admin liest standardmäßig das Token im Textkörper
login ( { commit } , userInfo ) {
const { username , password } = userInfo
return new Promise ( ( resolve , reject ) => {
login ( { username : username . trim ( ) , password : password } ) . then ( response => {
const { data } = response
commit ( 'SET_TOKEN' , data . token )
setToken ( data . token )
resolve ( )
} ) . catch ( error => {
reject ( error )
} )
} )
Verwenden Sie dann das Token, um die Berechtigungen beim Backend zu überprüfen
service . interceptors . request . use (
config => {
// do something before request is sent
return config
} ,
error => {
// do something with request error
console . log ( error ) // for debug
return Promise . reject ( error )
}
)
Ändern Sie es nun wie folgt: Das Backend speichert das Token direkt im Cookie-Header und setzt es auf httponly
@ Configuration
public class ShiroConfig {
// ... 其他设置
public SimpleCookie buildCookie () {
SimpleCookie simpleCookie = new SimpleCookie ( TOKEN_NAME );
simpleCookie . setPath ( "/" );
// 对服务器生成的TOKEN设置 HttpOnly 属性. 前端无法读写该TOKEN, 提供系统安全, 防止XSS攻击
simpleCookie . setHttpOnly ( true );
// 设置浏览器关闭时失效此Cookie
simpleCookie . setMaxAge (- 1 );
return simpleCookie ;
}
}
In einigen Fällen muss das Front-End noch eine Token-Überprüfung durchführen und das Token im Cookie kann nicht abgerufen werden. Daher wird im Antwortnachrichtentext der Anmeldeschnittstelle auch ein Token zurückgegeben, das angibt, dass die Anmeldung erfolgreich war Wird vom Front-End durchgeführt und nimmt nicht an der Back-End-Verifizierung teil.
Die Front-End-Routen werden in der Back-End-Datenbank gespeichert. Wenn sich der Benutzer anmeldet, wird der Routen-JSON vom Back-End angefordert und dann vom Front-End dynamisch hinzugefügt.
const actions = {
generateRoutes ( { commit } , roles ) {
return new Promise ( resolve => {
getUserFrontendPermissions ( ) . then ( response => {
let routeNodes = response . data . routeNodes
importComponent ( routeNodes )
commit ( 'SET_ROUTES' , routeNodes )
resolve ( routeNodes )
} )
} )
}
}
function importComponent ( routeNodes ) {
for ( var rn of routeNodes ) {
if ( rn . component == "Layout" ) {
rn . component = Layout
} else {
let componentPath = rn . component
rn . component = ( ) => import ( `@/views/ ${ componentPath } ` )
}
if ( rn . children && rn . children . length > 0 ) {
importComponent ( rn . children )
}
}
}
Die Hauptfunktion ist importComponent(routeNodes)
, die Komponenten rekursiv importiert.
Hierbei ist zu beachten, dass Sie beim Kompilieren von es6 durch Webpack und der dynamischen Einführung von import() keine Variablen übergeben können. Wenn Sie jedoch Variablen verwenden müssen, können Sie Webpack einen Teil der Informationen über eine Zeichenfolgenvorlage bereitstellen, z. B. import(
./path/${myFile}
), damit alle Module unter ./path während der Kompilierung kompiliert werden. Bitte beachten Sie die import()-Syntax in vue.
Das Backend verwendet Shiro zur Berechtigungsüberprüfung. Dies ist ein sehr interessantes Framework. Die Struktur des Codes ist klar und einfach.
@ Configuration
public class ShiroConfig {
/**
* 设置接口权限验证, 目前只针对api接口进行权限验证
*
* @param securityManager
* @return
*/
@ Bean ( name = "shiroFilter" )
public ShiroFilterFactoryBean shiroFilter ( SecurityManager securityManager ) {
LOGGER . info ( "start shiroFilter setting" );
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean ();
shiroFilterFactoryBean . setSecurityManager ( securityManager );
shiroFilterFactoryBean . setLoginUrl ( "/" );
shiroFilterFactoryBean . setSuccessUrl ( "/#/dashboard" );
shiroFilterFactoryBean . setUnauthorizedUrl ( "/403" );
Map < String , String > filterChainDefinitionMap = new LinkedHashMap <>();
Map < String , Filter > filtersMap = new LinkedHashMap <>();
filtersMap . put ( "apiAccessControlFilter" , new ApiAccessControlFilter ());
shiroFilterFactoryBean . setFilters ( filtersMap );
filterChainDefinitionMap . put ( "/static/**" , "anon" );
filterChainDefinitionMap . put ( "/#/login/**" , "anon" );
filterChainDefinitionMap . put ( "/api/user/auth/login" , "anon" );
filterChainDefinitionMap . put ( "/logout" , "logout" );
filterChainDefinitionMap . put ( "/api/**" , "apiAccessControlFilter" );
filterChainDefinitionMap . put ( "/**" , "logFilter" );
filterChainDefinitionMap . put ( "/**" , "authc" );
shiroFilterFactoryBean . setFilterChainDefinitionMap ( filterChainDefinitionMap );
LOGGER . info ( "shirFilter config fineshed" );
return shiroFilterFactoryBean ;
}
}
Derzeit sind nur die Einstellungen zur Berechtigungsüberprüfung für die Schnittstelle /api/**
festgelegt.
public class ApiAccessControlFilter extends AccessControlFilter {
private static final Logger LOGGER = LoggerFactory . getSystemLogger ( ApiAccessControlFilter . class );
@ Override
protected boolean isAccessAllowed ( ServletRequest request , ServletResponse response , Object mappedValue ) throws Exception {
// 开发环境中, 如果是OPTIONS预检请求则直接返回true TODO 这里想办法做的更加优雅些, 目前就是个补丁
if (! SpringUtil . isInProduction ()
&& request instanceof HttpServletRequest
&& "OPTIONS" . equals ((( HttpServletRequest ) request ). getMethod ())) {
return true ;
}
HttpServletRequest httpServletRequest = ( HttpServletRequest ) request ;
Subject subject = SecurityUtils . getSubject ();
boolean isAuthenticated = subject . isAuthenticated ();
boolean isPermitted = subject . isPermitted ( httpServletRequest . getRequestURI ());
LOGGER . info ( "鉴权完成, isPermitted:{}, isAuthenticated:{}" , isPermitted , isAuthenticated );
return isPermitted && isAuthenticated ;
}
private void trySetUserLog () {
LoggerLocalCache . INSTANCE . setUser ( UserUtil . getCurrentUserName ());
}
@ Override
protected boolean onAccessDenied ( ServletRequest request , ServletResponse servletResponse ) throws Exception {
LOGGER . info ( "访问被拒绝" );
Response response = ResponseCode . AUTH_FAIL . build ();
String result = JSON . toJSONString ( response );
servletResponse . getOutputStream (). write ( result . getBytes ( "UTF8" ));
servletResponse . flushBuffer ();
return false ;
}
}
Der Front-End-Entwicklungsprozess beinhaltet domänenübergreifende Probleme, daher müssen Front-End und Back-End gemeinsam geändert werden.
Frontend
if ( process . env . NODE_ENV === 'development' ) {
service . defaults . baseURL = 'http://localhost:9900/'
service . defaults . withCredentials = true
}
hinteres Ende
@ Configuration
public class ShiroConfig {
@ Bean
public CorsFilter corsFilter () {
// CORS配置信息
CorsConfiguration config = new CorsConfiguration ();
if (! SpringUtil . isInProduction ( applicationContext )) {
LOGGER . info ( "进行非生产模式CORS配置" );
config . addAllowedOrigin ( "*" );
config . setAllowCredentials ( true );
config . addAllowedMethod ( "*" );
config . addAllowedHeader ( "*" );
config . addExposedHeader ( "Set-Cookie" );
}
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource ();
configSource . registerCorsConfiguration ( "/**" , config );
return new CorsFilter ( configSource );
}
}
Diese Einstellung ist in der Get-Anfrage kein Problem, aber in der Post-Anfrage wird die Anfrage vorab geprüft und die OPTIONS-Anfrage zuerst gesendet, sodass die relevanten Einstellungen im ApiAccessControlFilter
in权限验证
vorgenommen werden.
if (! SpringUtil . isInProduction ()
&& request instanceof HttpServletRequest
&& "OPTIONS" . equals ((( HttpServletRequest ) request ). getMethod ())) {
return true ;
}
Das ist vorerst keine gute Lösung. Ich werde mir später eine bessere Lösung überlegen.
User
werden Benutzeranmeldeinformationen gespeichertRole
speichert RollenUserRoleRelation
Tabelle werden Benutzerrollenbeziehungen gespeichertBackendPermission
werden Backend-Berechtigungen gespeichert (alle Pfade werden automatisch in dieser Tabelle gespeichert, wenn der Server gestartet wird).RoleBackendPermissionRelation
Tabelle werden die Backend-Berechtigungen gespeichert, die der Rolle gehören.FrontendPermission
Tabelle werden Frontend-Routing-Informationen gespeichert.RoleFrontendPermissionRelation
Tabelle werden die Front-End-Routing-Informationen gespeichert, die der Rolle gehören. Um Probleme während des Entwicklungsprozesses einfacher beheben zu können, wird jede Anforderungs- und Antwortnachricht im HttpTraceLogFilter
gedruckt (Sie können sie in der Produktionsumgebung deaktivieren).
2020-04-02 11:37:51.963 [http-nio-9900-exec-2] INFO c.w.a.config.HttpTraceLogFilter(162) - [admin, /api/user/authority/getUserFrontendPermissions] Http 请求日志: HttpTraceLog{
method='GET',
path='/api/user/authority/getUserFrontendPermissions',
parameterMap='{}',
timeTaken=17,
time='2020-04-02T11:37:51.962',
status=200,
requestHeaders='
host: localhost:9900
connection: keep-alive
accept: application/json, text/plain, */*
sec-fetch-dest: empty
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
dnt: 1
sec-fetch-site: same-origin
sec-fetch-mode: cors
referer: http://localhost:9900/
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9,en;q=0.8
cookie: TTOKEN=0bbfbdca-40e5-4ee3-b19e-37d9b114cb47; frontend-token=undefined',
requestBody='',
responseHeaders='
Vary: Origin
Vary: Origin
Vary: Origin',
responseBody='{"code":20000,"message":"成功","data":{"routeNodes":[{"name":"权限配置","path":"/permission","component":"Layout","redirect":"/permission/frontend","meta":{"title":"权限配置","icon":"example"},"children":[{"name":"前端权限","path":"frontend","component":"permission/frontend/index","redirect":null,"meta":{"title":"前端权限","icon":"table"},"children":[]},{"name":"后端权限","path":"backend","component":"permission/backend/index","redirect":null,"meta":{"title":"后端权限","icon":"table"},"children":[]}]}]}}',}