Shiro é uma estrutura de controle de permissão leve com uma ampla gama de aplicações. O foco deste artigo é introduzir a integração de Shiro pela Spring e permitir que parâmetros dinâmicos, como @ReQuiresRoles, sejam suportados, estendendo as expressões EL da Spring. A introdução a Shiro não está dentro do escopo deste artigo. Se os leitores não sabem muito sobre Shiro, eles podem aprender as informações correspondentes através de seu site oficial. Há também um artigo sobre o InfoQ que fornece uma introdução abrangente ao Shiro e também é oficialmente recomendado. Seu endereço é https://www.infoq.com/articles/apache-shiro.
Shiro integra a primavera
Primeiro, você precisa adicionar Shiro-spring-xxx.jar ao seu projeto. Se você estiver usando o MAVEN para gerenciar seu projeto, poderá adicionar as seguintes dependências às suas dependências. Aqui está a versão mais recente 1.4.0 que selecionei.
<Depencency> <voundId> org.apache.shiro </frupiD> <ArtifactId> shiro-spring </artifactId> <versão> 1.4.0 </versão </dependency>
Em seguida, você precisa definir um shirofilter em seu web.xml e aplicá -lo para interceptar todas as solicitações que exigem controle de permissão, geralmente configuradas como /*. Além disso, o filtro precisa ser adicionado à frente para garantir que a solicitação seja controlada pela primeira vez através das permissões do Shiro após o que entrar. A classe correspondente de filtro aqui é configurada com DelegatingFilterProxy, que é um proxy de filtro fornecido pela mola. Você pode usar um feijão no recipiente de feijão da primavera como a instância do filtro atual, e o feijão correspondente levará o feijão correspondente ao nome do filtro. Portanto, a configuração a seguir procurará um feijão chamado Shirofilter no recipiente de feijão.
<filter> <filter-name> shirofilter </filter-name> <filter-Class> org.springframework.web.filter.delegatingFilterProxy </filter-class> <iit-param> </param-name> TargetFilterLifecycle </param-name> <airvalue> </param-value> <Filter-Name> shirofilter </filter-name> <url-tattern>/*</url-tattern> </filter-mapping>
Ao usar o Shiro de forma independente, você geralmente define um org.apache.shiro.web.servlet.shirofilter para fazer algo semelhante.
Em seguida, é definir nosso shirofilter no recipiente de feijão. Como segue, definimos um ShirofilterFactoryBean, que produzirá um feijão do tipo abstratohirofilter. Através do ShirofilterFactoryBean, podemos especificar um SecurityManager. O DefaultWebSecurityManager usado aqui precisa especificar um reino. Se o reino múltiplo precisar ser especificado, é especificado através de reinos. Para simplificar, usamos o TextConfigurationRealM com base na definição de texto diretamente. Use o LoginUrl para especificar o endereço de login, sucessão para especificar o endereço que precisa ser redirecionado após o sucesso do login e não autorizado para especificar a página prompt quando as permissões forem insuficientes. FilterChaindefinitions define a relação entre o URL e o filtro a ser usado. O alias do filtro no lado direito do sinal igual é o alias do filtro. Os alias padrão são definidos na classe de enumeração org.apache.shiro.web.filter.mgt.defaultfilter.
<bean id = "shirofilter"> <propriedade name = "securityManager" ref = "securityManager"/> <propriedade name = "loginurl" value = "/login.jsp"/> <names name = "successurl" value = "/home.jsp"/> <nome do nome "unauthurl" valunel = "/unuthor.jsp"/> <names = "unauthurl" "/"/unuthorized "" value = "/unauthorized.jsp"/> <propriedade name = "filterChaindefinitions"> <value>/admin/** = authc, paus [admin]/logout = logout # Outros endereços exigem que o usuário tenha logado em/** = authc, logger </value> </propriedade> </bean> <bean idan = " ref = "RealM"/> </ Bean> <bean id = "LifeCycleBeanPostProcessor"/>
<!-Por simplicidade, usaremos a implementação do reino baseado em texto aqui-> <bean id = "realm"> <propriedade name = "userDefinitions"> <value> user1 = pass1, role1, função2 user2 = pass2, função2, função3 admin = admin, admin, </value> </oilter
Se você precisar usar um filtro personalizado na definição do filtroChainndefinitions, poderá especificar o filtro personalizado e seu relacionamento de mapeamento de alias através dos filtros do ShirofilterFactoryBean. Por exemplo, como mostrado abaixo, adicionamos um filtro com Alias Logger e Filtro especificado /** com Alias Logger em FilterChaIndEfinitions.
<bean id = "shirofilter"> <propriedade name = "securityManager" ref = "securityManager"/> <propriedade name = "loginurl" value = "/login.jsp"/> <names name = "successurl" value = "/home.jsp"/> <nome do nome "unauthurl" valunel = "/unuthor.jsp"/> <names = "unauthurl" "/"/unuthorized "" value = "/unauthorized.jsp"/> <propriedade name = "filters"> <util: map> <entradas key = "logger"> <bean/> </entradas> </til: map> </propriedade> <propriedades name = "filterChaIndEndEfinitions"> <value>/admin/** = Authc, has has [logout]/logout = Logout = Logout = Logout = LOGOUT = LOGOUT = </value> </souse> </i bean>
De fato, a definição de alias de filtro que precisamos aplicar também pode ser definida diretamente pelos setfilters () do ShirofilterFactoryBean (), mas define diretamente o filtro correspondente correspondente no recipiente de feijão correspondente. Porque, por padrão, o ShirofilterFactoryBean registrará todos os feijões do tipo filtro no recipiente de feijão com seu alias de identificação nos filtros. Portanto, a definição acima é equivalente ao seguinte.
<bean id = "shirofilter"> <propriedade name = "securityManager" ref = "securityManager"/> <propriedade name = "loginurl" value = "/login.jsp"/> <names name = "successurl" value = "/home.jsp"/> <nome do nome "unauthurl" valunel = "/unuthor.jsp"/> <names = "unauthurl" "/"/unuthorized "" value = "/unauthorized.jsp"/> <propriedade name = "filterChaindefinitions"> <value>/admin/** = authc, funções [admin]/logout = logout # Outros endereços exigem que o usuário tenha logado em/** = authc, logger </value> </propriedade> </bean> <bean id = ""
Após as etapas acima, a integração de Shiro e Spring é concluída. At this time, any path we requested for the project will require us to log in, and will automatically jump to the path specified by loginUrl and let us enter the username/password to log in. At this time, we should provide a form to obtain the username through username, password through password, and then when submitting the login request, the request needs to be submitted to the address specified by loginUrl, but the request method needs to be changed to POST. O nome de usuário/senha usado quando o login é o nome de usuário/senha definido no TextConfigurationRealM. Com base em nossa configuração acima, você pode usar o user1/pass1, admin/admin, etc. Após o login -in com sucesso, ele saltará para o endereço especificado pelo parâmetro sucessurl. Se estivermos conectados usando o usuário1/pass1, também podemos tentar acessar/admin/index e, neste momento, iremos para não autorizados.jsp devido a permissões insuficientes.
Ativar suporte baseado em anotação
A integração básica exige que definamos todos os controles de permissão que o URL precisa aplicar nas filtragens do ShirofilterFactoryBean. Às vezes, isso não é tão flexível. Shiro nos fornece anotações que podem ser usadas após a integração da mola. Ele nos permite adicionar anotações correspondentes à classe ou método que exigem controle de permissão para definir as permissões necessárias para acessar a classe ou método. Se estiver na aula na definição, significa que chamar todos os métodos da classe requer permissões correspondentes (observe que ela precisa ser uma chamada externa, que é a limitação de proxy dinâmico). Para usar essas anotações, precisamos adicionar as duas definições de feijão a seguir ao recipiente de feijão da primavera, para que possamos determinar se o usuário possui as permissões correspondentes com base na definição de anotação em tempo de execução. Isso é alcançado através do mecanismo AOP da primavera. Se você não souber nada sobre a Spring AOP, pode consultar a "coluna de introdução da Spring AOP", escrita pelo autor. As duas definições de feijão a seguir, AuthorizationAttributesourCeadvisor define um consultor, que interceptará e verificará as permissões com base no método de configuração de anotação fornecido pelo Shiro. DefaultAdVisoraTOProxyCreator fornece a função de criar objetos proxy para classe marcada com anotações de controle de permissão fornecidas pelo Shiro e aplicar a AutorizationAttributesourCeadVisor ao interceptar a chamada do método de destino. Quando uma solicitação do usuário é interceptada e o usuário não possui permissão marcada no método ou classe correspondente, uma exceção org.apache.shiro.authz.authorizationException será lançada.
<bean depende-ON = "LifeCycleBeanPostProcessor"/> <Bean> <propriedade name = "SecurityManager" ref = "SecurityManager" // </ Bean>
Se <aop:config/>或<aop:aspectj-autoproxy/> já estiver definido em nosso contêiner de feijão, então o DefaultAdvisorautoProxycreator não poderá mais ser definido. Como os dois casos anteriores adicionarão automaticamente os feijões semelhantes ao DefaultAdvisorautoProxyCreator. Para obter mais informações sobre o DefaultAdvisoraToProxycreator, você também pode consultar o princípio do autor de criar objetos proxy automaticamente na Spring AOP.
As anotações de controle de permissão fornecidas por Shiro são as seguintes:
Requer as autenticação: o usuário precisa ser autenticado na sessão atual, ou seja, ele precisa fazer login com o nome de usuário/senha e não inclui o LembreteMe ME Automatic Login.
Requeruser: O usuário deve ser autenticado. Ele pode ser autenticado ao fazer login com o nome de usuário/senha nesta sessão, ou pode ser automaticamente conectado ao RememberMe.
RequerGuest: o usuário não está conectado.
RequerRoles: o usuário exige que a função especificada seja de propriedade.
Requer missões: o usuário requer as permissões especificadas.
Os três primeiros são fáceis de entender, enquanto os dois últimos são semelhantes. Aqui eu uso o @ReQuiresPermissions como exemplo. Primeiro, vamos alterar o reino definido acima e adicionar permissões à função. Dessa forma, nosso usuário1 terá permissões para Perm1, Perm2 e Perm3 e User2 terá permissões para Perm1, Perm3 e Perm4.
<bean id = "realM"> <propriedade name = "userDefinitions"> <Value> user1 = pass1, função1, função2 user2 = pass2, role2, role3 admin = admin, admin </value> </propriedades> property name = "roledefinitions"> <dalue> role1 = perm1, perm2 role2 = perm1, perm1, perm3 role3 = role3 = 3) </perm1/perm1/perm1, perm1 3 role3/perm1, </perm1/perm1) </perm1/perm1/perm1)
@ReQuiresPermissions pode ser adicionado a um método para especificar as permissões que precisam ser reivindicadas ao chamar o método. No código a seguir, especificamos que a permissão do Perm1 deve ser possuída ao acessar /Perm1. No momento, o usuário1 e o usuário2 podem ser acessados.
@RequestMapping ("/Perm1")@requerspermissions ("Perm1") Public Object Permission1 () {return "Perm1";}Se você precisar especificar que deve ter várias permissões ao mesmo tempo para acessar um método, poderá especificar as permissões que precisa especificar na forma de uma matriz (ao especificar um único atributo de matriz na anotação, você não pode adicionar aparelhos, mas quando precisar especificar várias permissões, é necessário). Por exemplo, da seguinte forma, especificamos que, ao acessar /Perm1AndPerm4, o usuário deve ter permissões Perm1 e Perm4. Neste momento, apenas o usuário2 pode acessá -lo, porque apenas possui Perm1 e Perm4 ao mesmo tempo.
@RequestMapping ("/Perm1AndPerm4")@requerspermissions ({"Perm1", "Perm4"}) public Object Perm1andPerm4 () {Return "Perm1andPerm4";}Quando várias permissões são especificadas ao mesmo tempo, a relação entre várias permissões é o relacionamento, ou seja, todas as permissões especificadas ao mesmo tempo são necessárias. Se você precisar apenas ter uma das múltiplas permissões especificadas para estar acessível, podemos especificar o relacionamento entre as ou entre múltiplas permissões através do lógico = lógico.or. Por exemplo, da seguinte forma, especificamos que, ao acessar /Perm1orPerm4, você só precisa ter permissões Perm1 ou Perm4, para que o usuário1 e o usuário2 possam acessar esse método.
@RequestMapping ("/Perm1orPerm4")@requerSperMissions (value = {"Perm1", "Perm4"}, logical = logical.or) public Object Perm1orPerm4 () {return "Perm1orPerm4";}O @ReQuiresPermissions também pode ser marcado na aula, indicando que, ao acessar métodos de classe externamente, você precisa ter permissões correspondentes. Por exemplo, a seguir, especificamos que precisamos ter permissão Perm2 no nível da classe, enquanto o método Index () não especifica que precisamos de permissão, mas ainda precisamos ter permissões especificadas no nível da classe ao acessar esse método. No momento, apenas o usuário1 pode acessá -lo.
@RestController@requestmapping ("/foo")@requerspermissions ("perm2") classe pública fooocontroller {@RequestMapping (método = requestMethod.get) public Object Index () {map <string, object> map = new hashmap <> (); map.put ("ABC", 123); mapa de retorno; }}Quando os níveis de classe e método tiverem @ReQuiresPermissions, o nível do método tem uma prioridade mais alta e apenas as permissões exigidas pelo nível do método serão verificadas no momento. Como segue, especificamos que a permissão do Perm2 é necessária no nível da classe e a permissão do Perm3 é necessária no nível do método. Então, ao acessar /Foo, você só precisa ter permissões Perm3 para acessar o método Index (). Portanto, neste momento, o usuário1 e o usuário2 podem acessar /foo.
@RestController @requestmapping ("/foo") @requerspermissions ("perm2") public class Foocontroller {@ReQuestMapping (método = requestmethod.get) @requiresPermissions ("Perm3") public Object Index () {Map <String, object> map = newHMap <> (); map.put ("ABC", 123); mapa de retorno; }}No entanto, se adicionarmos @ReQuiresRoles ("Role1") à classe no momento para especificar que precisamos ter função de função1 e, ao acessar /foo, precisamos ter o ROUSE1 especificado por @ReQuiresPermissions ("Perm3") no Role1 no método índice () na classe. Como requerroles e requer missões pertencem a definições de permissão de diferentes dimensões, Shiro as verificará uma vez durante a verificação, mas se a classe e o método tiverem anotações do mesmo tipo de definição de controle de permissão, a definição no método será baseada apenas na definição.
@RestController@requestmapping ("/foo")@requerspermissions ("Perm2")@requeresRoles ("role1") public class FooController {@ReQuestMapping (método = requestMethod.get) @requiiresPermissions ("Perm3") público map.put ("ABC", 123); mapa de retorno; }}Embora o exemplo use apenas requer missões, o uso de outras anotações de controle de permissão também é semelhante. Por favor, use outras anotações de amigos interessados.
Princípio das permissões de controle de anotação
As permissões especificadas acima são estáticas usando @ReQuiresPermissions. Um dos principais propósitos deste artigo é introduzir um método para tornar a dinâmica das permissões especificadas, estendendo a implementação. Mas antes de expandirmos, precisamos saber como funciona, ou seja, o princípio da implementação, antes que possamos expandir. Então, vamos dar uma olhada em como Shiro integra a primavera com @ReQuiresPermissions. Ao ativar o suporte para @ReQuiresPermissions, definimos o seguinte Bean, que é um consultor, que é herdado da StaticMethodMatcherPointCutadvisor. Sua lógica de correspondência de método é que, desde que a classe ou método tenha várias anotações de controle de permissão do Shiro, a lógica de processamento após a interceptação é especificada pelo conselho correspondente.
<Bean> <Propriedade name = "SecurityManager" Ref = "SecurityManager"/> </ Bean>
A seguir, é apresentado o código -fonte do AuthorizationAttributesourCeadvisor. Podemos ver que, em seu método construtor, aOpalliaCeanNotações que a autorizaMethodinterceptor é especificado por SetAdvice (), que é baseado na implementação do MethodInterceptor.
classe pública AuthorizationAttributesOrCeadVisor estende o staticMethMatcherPointPointCutadVisor {Private Static Final Logger Log = LoggerFactory.getLogger (AuthorizationAttributesourCeadvisor.class); Classe final estática privada <? estende a anotação> [] authz_annotation_classes = nova classe [] {requerspermissions.class, requerseroles.class, requerseser.class, requerguest.class, requersauthentication.class}; SecurityManager protegido SecurityManager = NULL; public AuthorizationAttributesourCeadvisor () {SetAdvice (novo aOpalliaCeanNoTationsAtorizingMethodIntercept ()); } public SecurityManager getSecurityManager () {return securityManager; } public void SetSecurityManager (org.apache.shiro.mgt.securityManager SecurityManager) {this.SecurityManager = SecurityManager; } correspondências booleanas públicas (método do método, classe TargetClass) {Método M = Método; if (isauthzannotationPresent (m)) {return true; } // O parâmetro 'Método' pode ser de uma interface que não possui a anotação. // Verifique se a implementação possui. if (TargetClass! = null) {tente {m = TargetClass.getMethod (m.getName (), m.getParameterTypes ()); Retorno isauthzannotationPresent (m) || isauthzannotationPresent (TargetClass); } catch (noschmethodException ignorado) {// O valor de retorno padrão é falso. Se não conseguirmos encontrar o método, obviamente // não há anotação; portanto, use o valor de retorno padrão. }} retornar false; } private boolean isauthzannotationPresent (classe <?> TargetClazz) {for (class <? Extende anotação> annclass: authz_annotation_classes) {anotação a = annotationutils.findannotation (Targetclaz, annclass); if (a! = null) {return true; }} retornar false; } private boolean isauthzannotationPresent (método método) {for (class <? Extende anotação> ANCLASS: authz_annotation_classes) {anotação a = annotationutils.findannotation (método, annclass); if (a! = null) {return true; }} retornar false; }}O código -fonte do AOPALLIANCEANNOTATIONATIONATORIZINGMETHODINTERCECTOR é o seguinte. O método Invoke da interface MethodInterceptor implementada nele chama o método Invoke da classe pai. Ao mesmo tempo, precisamos ver que algumas implementações AuthorizingAnnoTationMethodIntercept foram criadas em seu método construtor. Essas implementações são o núcleo de implementar o controle de permissão. Posteriormente, selecionaremos a classe de implementação de permissãoNotationMethodInterceptor para ver sua lógica de implementação específica.
classe pública aOpalliaCeanNotações AutorizandoMethodIntercept Ansenda ANOTAÇÕES AUTORIZINGMETHODOINTECTORMECTORMECTORMENTO MECHONINTERCECTOR {public AOPALLIANCEANNOTATIONATIONATIONINGIZINGMETHODODOINTECTIVECTION () {list <orizingAnnOnToTationMetodintercetor> interceptores = newListM; // Use um resolvedor de anotação específico da mola - o Spring's AnoTationUtils é melhor que o processo de resolução JDK // Raw JDK. AnoTationResolver Resolver = new SpringannotationResolver (); // Podemos reutilizar a mesma instância do resolvedor - ele não retém o estado: interceptores.add (novo roleanNotationMethOnintercept (Resolver)); interceptores.Add (New PermissionAnnotationMethodIntercept (Resolver)); interceptores.add (novo userannotationMethodIntercept (Resolver)); interceptores.Add (Novo GuestannotationMethodIntercept (Resolver)); setMethodInterceptores (interceptores); } protegido org.apache.shiro.aop.methodinvocation CreateMethodinVocation (objeto ImplSpecificMethodinVocation) {Final MethodInvocation mi = (MethodInvocation) ImplSpecificMethodinVocation; Retorne new org.apache.shiro.aop.methodinvocation () {public métod getMethod () {return mi.getMethod (); } public object [] getArgudes () {return mi.getarguments (); } public string tostring () {return "Method Invocation [" + mi.getMethod () + "]"; } public objeto prossegu () lança throwable {return mi.proeced (); } public objeto getThis () {return mi.getThis (); }}; } objeto protegido continueInvocation (objeto aOpallianiaCemethodinvocation) lança arremesso {MethodInvocation mi = (MethodInvocation) AOPALLIANCEMETHODINVOCATION; retornar mi.proeced (); } Public Object Invoke (MethodInvocation MethodInvocation) lança lançável {org.apache.shiro.aop.methodinvocation mi = createMethodinVocation (MethodInvocation); retornar super.invoke (mi); }}Ao analisar a implementação do método de invocado da classe pai, finalmente veremos que a lógica principal é chamar o método assertorizado, e a implementação desse método (o código -fonte é o seguinte) é determinar se o autorizando o AuthoringAnnotationMethodinterceptor suporta a verificação de permissão do método atual (ao julgar se ele tem seu apoio de apoio. Quando apoiado, seu método assertorizado será chamado para a verificação de permissão, e o AutorizandonOnTationMethodInterceptor, por sua vez, chamará o método assertorizado de autorização donloTationHandler.
o void protegido assertorized (MethodInvocation MethodInvocation) lança AuthorizationException {// A implementação padrão apenas garante que nenhum voto de nega seja lançado: coleção <autorizaçãoNotationMethodInterceptor> aamis = getMethodinterceptores (); if (aamis! = null &&! aamis.isEmpty ()) {for (autorizandonOnTationMethodintercept Aami: aamis) {if (aami.supports (MethodInvocation)) {aami.assertAtHorized (MethodInvocation); }}}}Em seguida, vamos olhar para o permissionannotationMethodInterceptor definido pelo AOPALLIANCEANNOTATIONATIONATORIZINGMETHODINTERCECTOR, o código -fonte é o seguinte. Combinando o código -fonte do AOPALLIANCEAN NOTATIONATIVAGILORIZINGMETHODIntercept e o código -fonte de permissionAnnotationMethOninterceptor, podemos ver que a PermissionAnnotationHandler e o SpringannotationResolver são especificados em PermissionAnnoThodintercept. PermissionAnnotationHandler é uma subclasse do autorizadorannOtationHandler. Portanto, nosso controle final de permissão é determinado pela implementação assertorizada do PermissionAnnotationHandler.
classe pública PermissionAnnotationMethodInterceptor estende AutorizandonOnTationMethodIntercept {public PermissionAnnotationMethOnintercept () {super (new PermissionAnnoTationHandler ()); } public PermissionAnnotationMethOninterceptor (AnoTationResolver resolver) {super (new PermissionAnnotationHandler (), Resolver); }}Em seguida, vejamos a implementação do método assertorizado assertorizados do PermissionAnnotationHandler, e o código completo é o seguinte. A partir da implementação, podemos ver que ela obterá o valor de permissão configurado da anotação, e a anotação aqui está a anotação requisition. Além disso, ao executar a verificação de permissão, usamos diretamente o valor do texto especificado ao definir a anotação. Começaremos daqui quando expandirmos mais tarde.
classe pública PermissionAnnotationHandler estende AuthorizingAnnoTationHandler {public permissionannotationHandler () {super (requerspermissions.class); } String protegida [] getAnnotationValue (anotação a) {requerPermissions rpannotation = (requer asermissões) a; return rpannotation.value (); } public void AssertAuthorized (Anotation A) lança AuthorizationException {if (! (A Instância de requer missões)) Return; Requer missões rpannotation = (requer missões) a; String [] perms = getAnnotationValue (a); Sujeito sujeito = getSUBject (); if (perms.Length == 1) {sujeito.CheckPermission (Perms [0]); retornar; } if (logical.and.equals (rpannotation.Logical ())) {getSUBJECT (). checkPermissions (perms); retornar; } if (logical.or.equals (rpannotation.logical ())) {// Evite o processamento de exceções sem vertigação - "atraso" lançando a exceção chamando Hasrole First Boolean HasatleastOnepermission = false; para (String Permission: Perms) if (getSuBject (). isperMitt (permissão)) hasatleastOnePermission = true; // Causa a exceção Se nenhuma das função corresponder, observe que a mensagem de exceção será um pouco enganadora se (! }}}Através da introdução anterior, sabemos que a anotação do parâmetro de método assertorizada do permissionAnnotationHandler é aprovada pela AutorizandoNotationMethOninterceptor ao chamar o método assertorizado de autorização dontotationHandler. O código -fonte é o seguinte. A partir do código -fonte, podemos ver que a anotação é obtida através do método GetAnnotation.
public void AssertAuthorized (MethodInvocation mi) lança AuthorizationException {try {((AuthorizingAnnotationHandler) Gethandler ()). AssertAuthorized (getAnnotation (mi)); } catch (autorizationException ae) {if (ae.getcause () == null) ae.initcause (new AuthorizationException ("não está autorizado a invocar o método:" + mi.getMethod ())); jogue ae; }}Andando nessa direção, encontraremos a implementação do método GetAnnotation do SpringannotationResolver, que é implementado da seguinte maneira. Como pode ser visto no código a seguir, é preferido procurar o método ao procurar anotações. Se não for encontrado no método, procurará a anotação correspondente da classe da chamada atual do método. A partir daqui, também podemos ver por que o que entra em vigor no método quando definimos o mesmo tipo de anotação de controle de permissão na classe e no método antes e, quando existe sozinha, o que é definido entra em vigor.
classe pública SpringannotationResolver implementa AnoTationResolver {public anotação getAnnotation (MethodInvocation mi, classe <? Extende anotação> clazz) {método m = mi.getMethod (); Anotação A = AnoTationUtils.findannotation (M, Clazz); if (a! = nulo) retorna a; // O objeto de método do MethodInvocation pode ser um método definido em uma interface. // No entanto, se a anotação existir na implementação da interface (e não // a própria interface), ela não estará no objeto do método acima. Em vez disso, precisamos // adquirir a representação do método do TargetClass e verificar diretamente a própria implementação // classe <?> TargetClass = mi.getThis (). GetClass (); m = classutils.getsmSpecificMethod (M, TargetClass); a = annotationutils.findannotation (m, clazz); if (a! = nulo) retorna a; // Veja se a classe tem a mesma anotação de retorno anotationutils.findannotation (mi.getThis (). GetClass (), clazz); }} Através da leitura do código -fonte acima, acredito que os leitores têm uma compreensão mais profunda do princípio das anotações de controle de permissão suportadas por Shiro depois de integrar a primavera. O código -fonte publicado acima é apenas alguns dos principais que o autor acha que são relativamente essenciais. Se você quiser saber o conteúdo completo em detalhes, leia o código completo por si mesmo, juntamente com as idéias mencionadas pelo autor.
Depois de entender esse princípio de controle de permissão com base nas anotações, os leitores também podem expandir de acordo com as necessidades comerciais reais.
Estendido usando expressões de primavera el
Suponha que agora exista uma interface como a seguinte, que possui um método de consulta que recebe um tipo de parâmetro. Vamos simplificá -lo aqui, assumindo que, desde que esse parâmetro seja recebido e valores diferentes sejam retornados.
interface pública RealService {Object Query (int tipo); }Esta interface está aberta ao mundo exterior. Este método pode ser solicitado através do URL correspondente. Definimos o método do controlador correspondente da seguinte maneira:
@RequestMapping ("/Service/{type}") public Object Query (@PathVariable ("type") int tipo) {return this.realservice.query (type);}O serviço de interface acima possui permissões para tipo ao realizar consulta. Nem todo usuário pode usar cada tipo para consultar e requer permissões correspondentes. Portanto, para o método do processador acima, precisamos adicionar controle de permissão e as permissões necessárias durante a mudança de controle dinamicamente com o tipo de parâmetro. Suponha que a definição de cada permissão do tipo seja a forma de consulta: tipo. Por exemplo, a permissão necessária quando o tipo = 1 é consulta: 1, e a permissão necessária quando tipo = 2 é consulta: 2. Quando não está integrado à primavera, fazemos isso da seguinte maneira:
@RequestMapping ("/Service/{type}") public Object Query (@PathVariable ("Type") int tipo) {SecurityUtils.getSubject (). CheckPermission ("Query:" + Type); Retorne this.RealService.Query (Type);}No entanto, após a integração com a primavera, as práticas acima são altamente acopladas e preferimos usar as anotações integradas para controlar as permissões. Para o cenário acima, preferimos especificar as permissões necessárias através do @ReQuiresPermissions, mas as permissões definidas no @ReQuiresPermissions são texto estático e corrigidas. Não pode atender às nossas necessidades dinâmicas. No momento, você pode pensar que podemos dividir o método de processamento do controlador em múltiplas e controlar as permissões separadamente. Por exemplo, o seguinte é:
@RequestMapping ("/Service/1")@requerspermissions ("Query: 1") public Object Service1 () {return this.realservice.Query (1);}@requersPermissions ("Query: 2")@requestMapp ("/Service/2") public Object Service2 () {Return: 2 ") this.RealService.Query (2);} //...@ requestmapping ("/service/200")@requerspermissions ("Query: 200") public Object Service200 () {return this.realservice.Query (200);}Tudo bem quando o intervalo de valor do tipo é relativamente pequeno, mas se houver 200 valores possíveis como o acima, será um pouco problemático enumerá -los exaustivamente para definir um método de processador separado e executar o controle de permissão. Além disso, se o valor das alterações do tipo no futuro, precisamos adicionar um novo método do processador. Portanto, a melhor maneira é fazer com que o @ReQuiresPermissions suportem definições de permissão dinâmica, mantendo o suporte da definição estática. Através da análise anterior, sabemos que o ponto de entrada é de permissionannotationHandler e não fornece extensões para verificação de permissão. Se queremos expandi -lo, a maneira simples é substituí -lo como um todo. No entanto, as permissões que precisamos processar dinamicamente estão relacionadas aos parâmetros do método, e os parâmetros do método não podem ser obtidos no HandiMAnnotationHandler. Por esse motivo, não podemos substituir diretamente o permissionannotationhandler. PermissionAnnotationHandler é chamado por PermissionAnnotationMethodInterceptor. Os parâmetros do método podem ser obtidos quando a PermissionAnnotationHandler é chamada no método assertorizado de sua classe pai AuthorizingAnnotationMethodInterceptor. Por esse motivo, nosso ponto de extensão é selecionado na classe PermissionAnnotationMethodInterceptor, e também precisamos substituí -lo como um todo. As expressões EL da Spring podem suportar valores de parâmetros do método de análise. Aqui escolhemos apresentar as expressões EL da Spring. Quando o @ReQuiresPermissions define permissões, você pode usar as expressões da Spring EL para introduzir parâmetros de método. Ao mesmo tempo, para levar em consideração o texto estático. Aqui está um modelo de expressão da primavera. Para o modelo de expressão de El Spring, consulte esta postagem do blog. Definimos nossa própria permissãoNotationMethodInterceptor, herdá -lo do permissionAnnotationMethodinterceptor e substituímos o método assertorado. A lógica de implementação do método refere -se à lógica no PermissionAnnotationHandler, mas a definição de permissão em @ReQuiresPermissions utilizados é o resultado do uso da expressão da primavera com base no método atualmente chamado como resultado de analisar o CONTECONTECONTEXT. A seguir, é apresentada a nossa própria definição de permissão da implementação do MethodInterceptor.
classe pública selfpermissionannotationMethodInterceptor estende a permissãoNotationMethodIntercept {private Final SpelExpressionParser Parser = new SpelExpressionParser (); Parâmetros final privado ParamenameDiscoverer paramNamediscoverer = new defaultParameternamediscoverer (); Private Final MatplateParserContext templateParserContext = new templateParserContext (); public SelfPermissionAnnotationMethodInterceptor(AnnotationResolver resolver) { super(resolver); } @Override public void assertAuthorized(MethodInvocation mi) throws AuthorizationException { Annotation annotation = super.getAnnotation(mi); RequiresPermissions permAnnotation = (RequiresPermissions) annotation; String[] perms = permAnnotation.value(); EvaluationContext evaluationContext = new MethodBasedEvaluationContext(null, mi.getMethod(), mi.getArguments(), paramNameDiscoverer); for (int i=0; i<perms.length; i++) { Expression expression = this.parser.parseExpression(perms[i], templateParserContext); //Replace the original permission definition with the permission definition parsed by Spring EL expression perms[i] = expression.getValue(evaluationContext, String.class); } Subject subject = getSubject(); if (perms.length == 1) { subject.checkPermission(perms[0]); retornar; } if (Logical.AND.equals(permAnnotation.logical())) { getSubject().checkPermissions(perms); retornar; } if (Logical.OR.equals(permAnnotation.logical())) { // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first boolean hasAtLeastOnePermission = false; for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true; // Cause the exception if none of the role match, note that the exception message will be a bit misleading if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]); }}}定义了自己的PermissionAnnotationMethodInterceptor后,我们需要替换原来的PermissionAnnotationMethodInterceptor为我们自己的PermissionAnnotationMethodInterceptor。根据前面介绍的Shiro整合Spring后使用@RequiresPermissions等注解的原理我们知道PermissionAnnotationMethodInterceptor是由AopAllianceAnnotationsAuthorizingMethodInterceptor指定的,而后者又是由AuthorizationAttributeSourceAdvisor指定的。为此我们需要在定义AuthorizationAttributeSourceAdvisor时通过显示定义AopAllianceAnnotationsAuthorizingMethodInterceptor的方式显示的定义其中的AuthorizingAnnotationMethodInterceptor,然后把自带的PermissionAnnotationMethodInterceptor替换为我们自定义的SelfAuthorizingAnnotationMethodInterceptor。替换后的定义如下:
<bean> <property name="securityManager" ref="securityManager"/> <property name="advice"> <bean> <property name="methodInterceptors"> <util:list> <bean c:resolver-ref="springAnnotationResolver"/> <!-- 使用自定义的PermissionAnnotationMethodInterceptor --> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> <bean c:resolver-ref="springAnnotationResolver"/> </util:list> </property> </bean> </property></bean><bean id="springAnnotationResolver"/>
为了演示前面示例的动态的权限,我们把角色与权限的关系调整如下,让role1、role2和role3分别拥有query:1、query:2和query:3的权限。此时user1将拥有query:1和query:2的权限。
<bean id="realm"> <property name="userDefinitions"> <value> user1=pass1,role1,role2 user2=pass2,role2,role3 admin=admin,admin </value> </property> <property name="roleDefinitions"> <value> role1=perm1,perm2,query:1 role2=perm1,perm3,query:2 role3=perm3,perm4,query:3 </value> </property></bean>
此时@RequiresPermissions中指定权限时就可以使用Spring EL表达式支持的语法了。因为我们在定义SelfPermissionAnnotationMethodInterceptor时已经指定了应用基于模板的表达式解析,此时权限中定义的文本都将作为文本解析,动态的部分默认需要使用#{前缀和}后缀包起来(这个前缀和后缀是可以指定的,但是默认就好)。在动态部分中可以使用#前缀引用变量,基于方法的表达式解析中可以使用参数名或p参数索引的形式引用方法参数。所以上面我们需要动态的权限的query方法的@RequiresPermissions定义如下。
@RequestMapping("/service/{type}")@RequiresPermissions("query:#{#type}")public Object query(@PathVariable("type") int type) { return this.realService.query(type);}这样user1在访问/service/1和/service/2是OK的,但是在访问/service/3和/service/300时会提示没有权限,因为user1没有query:3和query:300的权限。
Resumir
The above is a detailed explanation of Spring integrating Shiro and expanding the use of EL expressions introduced by the editor. Espero que seja útil para todos. Se você tiver alguma dúvida, deixe -me uma mensagem e o editor responderá a você a tempo. Muito obrigado pelo seu apoio ao site wulin.com!