authorization
parent
e21234ed3f
commit
a341faaebb
|
@ -0,0 +1,105 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="Spring" name="Spring">
|
||||||
|
<configuration />
|
||||||
|
</facet>
|
||||||
|
<facet type="web" name="Web">
|
||||||
|
<configuration>
|
||||||
|
<webroots />
|
||||||
|
<sourceRoots>
|
||||||
|
<root url="file://$MODULE_DIR$/src/main/java" />
|
||||||
|
<root url="file://$MODULE_DIR$/src/main/resources" />
|
||||||
|
</sourceRoots>
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
|
||||||
|
<output url="file://$MODULE_DIR$/target/classes" />
|
||||||
|
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.11.2" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.11.2" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.26" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: javax.annotation:javax.annotation-api:1.3.2" level="project" />
|
||||||
|
<orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.23" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.9" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.9" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.9" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:9.0.21" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:9.0.21" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.21" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.0.17.Final" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: javax.validation:validation-api:2.0.1.Final" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.2.Final" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.1.8.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.1.8.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.1.8.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.1.8.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.1.8.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.1.8.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-test:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-test:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.jayway.jsonpath:json-path:2.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: net.minidev:json-smart:2.3" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: net.minidev:accessors-smart:1.2" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.ow2.asm:asm:5.0.4" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.26" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: junit:junit:4.12" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.assertj:assertj-core:3.11.1" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.mockito:mockito-core:2.23.4" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: net.bytebuddy:byte-buddy:1.9.13" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: net.bytebuddy:byte-buddy-agent:1.9.13" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.objenesis:objenesis:2.6" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.hamcrest:hamcrest-library:1.3" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.skyscreamer:jsonassert:1.5.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.vaadin.external.google:android-json:0.0.20131108.vaadin1" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.1.8.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.1.8.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework:spring-test:5.1.8.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.xmlunit:xmlunit-core:2.6.2" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-spring-boot-web-starter:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-spring-boot-starter:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-spring:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-core:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-lang:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-cache:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-crypto-hash:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-crypto-core:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-crypto-cipher:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-config-core:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-config-ogdl:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: commons-beanutils:commons-beanutils:1.9.3" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: commons-collections:commons-collections:3.2.2" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-event:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-web:1.4.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.1.6.RELEASE" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.auth0:java-jwt:3.2.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.9.9" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.9.0" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.9.9" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.11" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.bouncycastle:bcprov-jdk15on:1.55" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.5" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.11" level="project" />
|
||||||
|
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>SpringBoot2</artifactId>
|
||||||
|
<groupId>zz</groupId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>SpringBootShiroJWT</artifactId>
|
||||||
|
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
<shiro.spring.version>1.4.0</shiro.spring.version>
|
||||||
|
<jwt.auth0.version>3.2.0</jwt.auth0.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 使用redis做数据缓存,如果不需要可不依赖
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.shiro</groupId>
|
||||||
|
<artifactId>shiro-spring-boot-web-starter</artifactId>
|
||||||
|
<version>${shiro.spring.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
<version>${jwt.auth0.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.5</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.7</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<skipTests>true</skipTests>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.github.demo;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ShiroWebApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ShiroWebApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.github.demo.configuration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.apache.shiro.authc.SimpleAuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
|
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
|
||||||
|
import org.apache.shiro.authz.AuthorizationInfo;
|
||||||
|
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||||
|
import org.apache.shiro.crypto.hash.Sha256Hash;
|
||||||
|
import org.apache.shiro.realm.AuthorizingRealm;
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
|
import org.apache.shiro.util.ByteSource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.github.demo.dto.UserDto;
|
||||||
|
import com.github.demo.service.UserService;
|
||||||
|
|
||||||
|
public class DbShiroRealm extends AuthorizingRealm {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(DbShiroRealm.class);
|
||||||
|
|
||||||
|
private static final String encryptSalt = "F12839WhsnnEV$#23b";
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
public DbShiroRealm(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.setCredentialsMatcher(new HashedCredentialsMatcher(Sha256Hash.ALGORITHM_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(AuthenticationToken token) {
|
||||||
|
return token instanceof UsernamePasswordToken;
|
||||||
|
}
|
||||||
|
// 登录验证
|
||||||
|
@Override
|
||||||
|
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
|
||||||
|
UsernamePasswordToken userpasswordToken = (UsernamePasswordToken)token;
|
||||||
|
String username = userpasswordToken.getUsername();
|
||||||
|
UserDto user = userService.getUserInfo(username);
|
||||||
|
if(user == null)
|
||||||
|
throw new AuthenticationException("用户名或者密码错误");
|
||||||
|
|
||||||
|
return new SimpleAuthenticationInfo(user, user.getEncryptPwd(), ByteSource.Util.bytes(encryptSalt), "dbRealm");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
||||||
|
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
|
||||||
|
UserDto user = (UserDto) principals.getPrimaryPrincipal();
|
||||||
|
List<String> roles = user.getRoles();
|
||||||
|
if(roles == null) {
|
||||||
|
roles = userService.getUserRoles(user.getUserId());
|
||||||
|
user.setRoles(roles);
|
||||||
|
}
|
||||||
|
if (roles != null)
|
||||||
|
simpleAuthorizationInfo.addRoles(roles);
|
||||||
|
|
||||||
|
return simpleAuthorizationInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.github.demo.configuration;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.JWTVerifier;
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
|
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||||
|
import com.github.demo.dto.UserDto;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.apache.shiro.authc.credential.CredentialsMatcher;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
|
public class JWTCredentialsMatcher implements CredentialsMatcher {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(JWTCredentialsMatcher.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
|
||||||
|
String token = (String) authenticationToken.getCredentials();
|
||||||
|
Object stored = authenticationInfo.getCredentials();
|
||||||
|
String salt = stored.toString();
|
||||||
|
|
||||||
|
UserDto user = (UserDto)authenticationInfo.getPrincipals().getPrimaryPrincipal();
|
||||||
|
try {
|
||||||
|
Algorithm algorithm = Algorithm.HMAC256(salt);
|
||||||
|
JWTVerifier verifier = JWT.require(algorithm)
|
||||||
|
.withClaim("username", user.getUsername())
|
||||||
|
.build();
|
||||||
|
verifier.verify(token);
|
||||||
|
return true;
|
||||||
|
} catch (UnsupportedEncodingException | JWTVerificationException e) {
|
||||||
|
log.error("Token Error:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.github.demo.configuration;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.*;
|
||||||
|
import org.apache.shiro.authz.AuthorizationInfo;
|
||||||
|
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||||
|
import org.apache.shiro.realm.AuthorizingRealm;
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.github.demo.dto.UserDto;
|
||||||
|
import com.github.demo.service.UserService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义身份认证
|
||||||
|
* 基于HMAC( 散列消息认证码)的控制域
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class JWTShiroRealm extends AuthorizingRealm {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(JWTShiroRealm.class);
|
||||||
|
|
||||||
|
protected UserService userService;
|
||||||
|
|
||||||
|
public JWTShiroRealm(UserService userService){
|
||||||
|
this.userService = userService;
|
||||||
|
this.setCredentialsMatcher(new JWTCredentialsMatcher());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(AuthenticationToken token) {
|
||||||
|
return token instanceof JWTToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
|
||||||
|
* 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
|
||||||
|
JWTToken jwtToken = (JWTToken) authcToken;
|
||||||
|
String token = jwtToken.getToken();
|
||||||
|
|
||||||
|
UserDto user = userService.getJwtTokenInfo(JwtUtils.getUsername(token));
|
||||||
|
if(user == null)
|
||||||
|
throw new AuthenticationException("token过期,请重新登录");
|
||||||
|
|
||||||
|
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getSalt(), "jwtRealm");
|
||||||
|
|
||||||
|
return authenticationInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
||||||
|
return new SimpleAuthorizationInfo();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.github.demo.configuration;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.HostAuthenticationToken;
|
||||||
|
|
||||||
|
public class JWTToken implements HostAuthenticationToken {
|
||||||
|
private static final long serialVersionUID = 9217639903967592166L;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
public JWTToken(String token) {
|
||||||
|
this(token, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JWTToken(String token, String host) {
|
||||||
|
this.token = token;
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken(){
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
return token + ':' + host;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package com.github.demo.configuration;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
|
import com.auth0.jwt.exceptions.JWTDecodeException;
|
||||||
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class JwtUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得token中的信息无需secret解密也能获得
|
||||||
|
* @return token中包含的签发时间
|
||||||
|
*/
|
||||||
|
public static Date getIssuedAt(String token) {
|
||||||
|
try {
|
||||||
|
DecodedJWT jwt = JWT.decode(token);
|
||||||
|
return jwt.getIssuedAt();
|
||||||
|
} catch (JWTDecodeException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得token中的信息无需secret解密也能获得
|
||||||
|
* @return token中包含的用户名
|
||||||
|
*/
|
||||||
|
public static String getUsername(String token) {
|
||||||
|
try {
|
||||||
|
DecodedJWT jwt = JWT.decode(token);
|
||||||
|
return jwt.getClaim("username").asString();
|
||||||
|
} catch (JWTDecodeException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成签名,expireTime后过期
|
||||||
|
* @param username 用户名
|
||||||
|
* @param time 过期时间s
|
||||||
|
* @return 加密的token
|
||||||
|
*/
|
||||||
|
public static String sign(String username, String salt, long time) {
|
||||||
|
try {
|
||||||
|
Date date = new Date(System.currentTimeMillis()+time*1000);
|
||||||
|
Algorithm algorithm = Algorithm.HMAC256(salt);
|
||||||
|
// 附带username信息
|
||||||
|
return JWT.create()
|
||||||
|
.withClaim("username", username)
|
||||||
|
.withExpiresAt(date)
|
||||||
|
.withIssuedAt(new Date())
|
||||||
|
.sign(algorithm);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token是否过期
|
||||||
|
* @return true:过期
|
||||||
|
*/
|
||||||
|
public static boolean isTokenExpired(String token) {
|
||||||
|
Date now = Calendar.getInstance().getTime();
|
||||||
|
DecodedJWT jwt = JWT.decode(token);
|
||||||
|
return jwt.getExpiresAt().before(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机盐,长度32位
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String generateSalt(){
|
||||||
|
SecureRandomNumberGenerator secureRandom = new SecureRandomNumberGenerator();
|
||||||
|
String hex = secureRandom.nextBytes(16).toHex();
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.github.demo.configuration;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.server.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class ResponseHeaderAdvice implements ResponseBodyAdvice<Object> {
|
||||||
|
@Override
|
||||||
|
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass,
|
||||||
|
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
|
||||||
|
ServletServerHttpRequest serverRequest = (ServletServerHttpRequest)serverHttpRequest;
|
||||||
|
ServletServerHttpResponse serverResponse = (ServletServerHttpResponse)serverHttpResponse;
|
||||||
|
if(serverRequest == null || serverResponse == null
|
||||||
|
|| serverRequest.getServletRequest() == null || serverResponse.getServletResponse() == null) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于未添加跨域消息头的响应进行处理
|
||||||
|
HttpServletRequest request = serverRequest.getServletRequest();
|
||||||
|
HttpServletResponse response = serverResponse.getServletResponse();
|
||||||
|
String originHeader = "Access-Control-Allow-Origin";
|
||||||
|
if(!response.containsHeader(originHeader)) {
|
||||||
|
String origin = request.getHeader("Origin");
|
||||||
|
if(origin == null) {
|
||||||
|
String referer = request.getHeader("Referer");
|
||||||
|
if(referer != null)
|
||||||
|
origin = referer.substring(0, referer.indexOf("/", 7));
|
||||||
|
}
|
||||||
|
response.setHeader("Access-Control-Allow-Origin", origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
String allowHeaders = "Access-Control-Allow-Headers";
|
||||||
|
if(!response.containsHeader(allowHeaders))
|
||||||
|
response.setHeader(allowHeaders, request.getHeader(allowHeaders));
|
||||||
|
|
||||||
|
String allowMethods = "Access-Control-Allow-Methods";
|
||||||
|
if(!response.containsHeader(allowMethods))
|
||||||
|
response.setHeader(allowMethods, "GET,POST,OPTIONS,HEAD");
|
||||||
|
|
||||||
|
String exposeHeaders = "access-control-expose-headers";
|
||||||
|
if(!response.containsHeader(exposeHeaders))
|
||||||
|
response.setHeader(exposeHeaders, "x-auth-token");
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package com.github.demo.configuration;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.Authenticator;
|
||||||
|
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
|
||||||
|
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
|
||||||
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
|
import org.apache.shiro.mgt.SessionStorageEvaluator;
|
||||||
|
import org.apache.shiro.realm.Realm;
|
||||||
|
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||||
|
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
|
||||||
|
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
|
||||||
|
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.github.demo.filter.AnyRolesAuthorizationFilter;
|
||||||
|
import com.github.demo.filter.JwtAuthFilter;
|
||||||
|
import com.github.demo.service.UserService;
|
||||||
|
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shiro配置类
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class ShiroConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean<Filter> filterRegistrationBean(SecurityManager securityManager,UserService userService) throws Exception{
|
||||||
|
FilterRegistrationBean<Filter> filterRegistration = new FilterRegistrationBean<Filter>();
|
||||||
|
filterRegistration.setFilter((Filter)shiroFilter(securityManager, userService).getObject());
|
||||||
|
filterRegistration.addInitParameter("targetFilterLifecycle", "true");
|
||||||
|
filterRegistration.setAsyncSupported(true);
|
||||||
|
filterRegistration.setEnabled(true);
|
||||||
|
filterRegistration.setDispatcherTypes(DispatcherType.REQUEST,DispatcherType.ASYNC);
|
||||||
|
|
||||||
|
return filterRegistration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Authenticator authenticator(UserService userService) {
|
||||||
|
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
|
||||||
|
authenticator.setRealms(Arrays.asList(jwtShiroRealm(userService), dbShiroRealm(userService)));
|
||||||
|
authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
|
||||||
|
return authenticator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected SessionStorageEvaluator sessionStorageEvaluator(){
|
||||||
|
DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
|
||||||
|
sessionStorageEvaluator.setSessionStorageEnabled(false);
|
||||||
|
return sessionStorageEvaluator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("dbRealm")
|
||||||
|
public Realm dbShiroRealm(UserService userService) {
|
||||||
|
DbShiroRealm myShiroRealm = new DbShiroRealm(userService);
|
||||||
|
return myShiroRealm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("jwtRealm")
|
||||||
|
public Realm jwtShiroRealm(UserService userService) {
|
||||||
|
JWTShiroRealm myShiroRealm = new JWTShiroRealm(userService);
|
||||||
|
return myShiroRealm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置过滤器
|
||||||
|
*/
|
||||||
|
@Bean("shiroFilter")
|
||||||
|
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, UserService userService) {
|
||||||
|
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
|
||||||
|
factoryBean.setSecurityManager(securityManager);
|
||||||
|
Map<String, Filter> filterMap = factoryBean.getFilters();
|
||||||
|
filterMap.put("authcToken", createAuthFilter(userService));
|
||||||
|
filterMap.put("anyRole", createRolesFilter());
|
||||||
|
factoryBean.setFilters(filterMap);
|
||||||
|
factoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());
|
||||||
|
|
||||||
|
return factoryBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected ShiroFilterChainDefinition shiroFilterChainDefinition() {
|
||||||
|
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
|
||||||
|
chainDefinition.addPathDefinition("/login", "noSessionCreation,anon");
|
||||||
|
chainDefinition.addPathDefinition("/logout", "noSessionCreation,authcToken[permissive]");
|
||||||
|
chainDefinition.addPathDefinition("/image/**", "anon");
|
||||||
|
chainDefinition.addPathDefinition("/admin/**", "noSessionCreation,authcToken,anyRole[admin,manager]"); //只允许admin或manager角色的用户访问
|
||||||
|
chainDefinition.addPathDefinition("/article/list", "noSessionCreation,authcToken");
|
||||||
|
chainDefinition.addPathDefinition("/article/*", "noSessionCreation,authcToken[permissive]");
|
||||||
|
chainDefinition.addPathDefinition("/**", "noSessionCreation,authcToken");
|
||||||
|
return chainDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JwtAuthFilter createAuthFilter(UserService userService){
|
||||||
|
return new JwtAuthFilter(userService);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AnyRolesAuthorizationFilter createRolesFilter(){
|
||||||
|
return new AnyRolesAuthorizationFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.github.demo.configuration;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
|
||||||
|
import org.springframework.web.servlet.config.annotation.*;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WebConfiguration extends WebMvcConfigurationSupport{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
registry.addMapping("/**")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.allowedMethods("*")
|
||||||
|
.allowedOrigins("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
|
||||||
|
configurer.setTaskExecutor(new ConcurrentTaskExecutor(Executors.newFixedThreadPool(3)));
|
||||||
|
configurer.setDefaultTimeout(30000);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.github.demo.controller;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import com.github.demo.dto.ArticleDto;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/article")
|
||||||
|
public class ArticleController {
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ResponseEntity<List<ArticleDto>> list(){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<ArticleDto> read(@PathVariable Long id){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.github.demo.controller;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.github.demo.dto.UserDto;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class AsyncRequestController {
|
||||||
|
|
||||||
|
@GetMapping("/async")
|
||||||
|
public Callable<UserDto> doAsync(){
|
||||||
|
return ()->{
|
||||||
|
Thread.sleep(5000);
|
||||||
|
return (UserDto)SecurityUtils.getSubject().getPrincipal();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.github.demo.controller;
|
||||||
|
|
||||||
|
import com.github.demo.dto.UserDto;
|
||||||
|
import com.github.demo.service.UserService;
|
||||||
|
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class LoginController {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(LoginController.class);
|
||||||
|
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
public LoginController(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名密码登录
|
||||||
|
* @param request
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/login")
|
||||||
|
public ResponseEntity<Void> login(@RequestBody UserDto loginInfo, HttpServletRequest request, HttpServletResponse response){
|
||||||
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
try {
|
||||||
|
UsernamePasswordToken token = new UsernamePasswordToken(loginInfo.getUsername(), loginInfo.getPassword());
|
||||||
|
subject.login(token);
|
||||||
|
|
||||||
|
UserDto user = (UserDto) subject. getPrincipal();
|
||||||
|
String newToken = userService.generateJwtToken(user.getUsername());
|
||||||
|
response.setHeader("x-auth-token", newToken);
|
||||||
|
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
} catch (AuthenticationException e) {
|
||||||
|
logger.error("User {} login fail, Reason:{}", loginInfo.getUsername(), e.getMessage());
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/logout")
|
||||||
|
public ResponseEntity<Void> logout() {
|
||||||
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
if(subject.getPrincipals() != null) {
|
||||||
|
UserDto user = (UserDto)subject.getPrincipals().getPrimaryPrincipal();
|
||||||
|
userService.deleteLoginInfo(user.getUsername());
|
||||||
|
}
|
||||||
|
SecurityUtils.getSubject().logout();
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package com.github.demo.dto;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class ArticleDto implements java.io.Serializable{
|
||||||
|
private static final long serialVersionUID = -2440471074054288487L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String title;
|
||||||
|
private String author;
|
||||||
|
private Date issueTime;
|
||||||
|
private Date created;
|
||||||
|
private Date modified;
|
||||||
|
private Long createUserId;
|
||||||
|
private String createUserName;
|
||||||
|
private Integer status; //0:待发布, 1:已发布, 2:已删除
|
||||||
|
private String content;
|
||||||
|
private String headImgUrl; //标题图片
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String auth) {
|
||||||
|
this.author = auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModified(Date modified) {
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getIssueTime() {
|
||||||
|
return issueTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIssueTime(Date issueTime) {
|
||||||
|
this.issueTime = issueTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCreateUserId() {
|
||||||
|
return createUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateUserId(Long createUserId) {
|
||||||
|
this.createUserId = createUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateUserName() {
|
||||||
|
return createUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateUserName(String createUserName) {
|
||||||
|
this.createUserName = createUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeadImgUrl() {
|
||||||
|
return headImgUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeadImgUrl(String headImgUrl) {
|
||||||
|
this.headImgUrl = headImgUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.github.demo.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户对象
|
||||||
|
*/
|
||||||
|
public class UserDto implements Serializable {
|
||||||
|
private static final long serialVersionUID = -9077975168976887742L;
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private char[] password;
|
||||||
|
private String encryptPwd;
|
||||||
|
private Long userId;
|
||||||
|
private String salt;
|
||||||
|
private List<String> roles;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char[] getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(char[] password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSalt() {
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSalt(String salt) {
|
||||||
|
this.salt = salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoles(List<String> roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEncryptPwd() {
|
||||||
|
return encryptPwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncryptPwd(String encryptPwd) {
|
||||||
|
this.encryptPwd = encryptPwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.github.demo.filter;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
|
||||||
|
import org.apache.shiro.web.util.WebUtils;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class AnyRolesAuthorizationFilter extends AuthorizationFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postHandle(ServletRequest request, ServletResponse response){
|
||||||
|
request.setAttribute("anyRolesAuthFilter.FILTERED", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
|
||||||
|
Boolean afterFiltered = (Boolean)(servletRequest.getAttribute("anyRolesAuthFilter.FILTERED"));
|
||||||
|
if( BooleanUtils.isTrue(afterFiltered))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Subject subject = getSubject(servletRequest, servletResponse);
|
||||||
|
String[] rolesArray = (String[]) mappedValue;
|
||||||
|
if (rolesArray == null || rolesArray.length == 0) { //没有角色限制,有权限访问
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (String role : rolesArray) {
|
||||||
|
if (subject.hasRole(role)) //若当前用户是rolesArray中的任何一个,则有权限访问
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
|
||||||
|
HttpServletResponse httpResponse = WebUtils.toHttp(response);
|
||||||
|
httpResponse.setCharacterEncoding("UTF-8");
|
||||||
|
httpResponse.setContentType("application/json;charset=utf-8");
|
||||||
|
httpResponse.setStatus(HttpStatus.SC_UNAUTHORIZED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package com.github.demo.filter;
|
||||||
|
|
||||||
|
import com.github.demo.configuration.JWTToken;
|
||||||
|
import com.github.demo.configuration.JwtUtils;
|
||||||
|
import com.github.demo.dto.UserDto;
|
||||||
|
import com.github.demo.service.UserService;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
|
||||||
|
import org.apache.shiro.web.util.WebUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.context.request.async.WebAsyncManager;
|
||||||
|
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class JwtAuthFilter extends AuthenticatingFilter {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(JwtAuthFilter.class);
|
||||||
|
|
||||||
|
private static final int tokenRefreshInterval = 300;
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
public JwtAuthFilter(UserService userService){
|
||||||
|
this.userService = userService;
|
||||||
|
this.setLoginUrl("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
|
||||||
|
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
|
||||||
|
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) //对于OPTION请求做拦截,不做token校验
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return super.preHandle(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postHandle(ServletRequest request, ServletResponse response){
|
||||||
|
this.fillCorsHeader(WebUtils.toHttp(request), WebUtils.toHttp(response));
|
||||||
|
request.setAttribute("jwtShiroFilter.FILTERED", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||||
|
if(this.isLoginRequest(request, response))
|
||||||
|
return true;
|
||||||
|
Boolean afterFiltered = (Boolean)(request.getAttribute("jwtShiroFilter.FILTERED"));
|
||||||
|
if( BooleanUtils.isTrue(afterFiltered))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
boolean allowed = false;
|
||||||
|
try {
|
||||||
|
allowed = executeLogin(request, response);
|
||||||
|
} catch(IllegalStateException e){ //not found any token
|
||||||
|
log.error("Not found any token");
|
||||||
|
}catch (Exception e) {
|
||||||
|
log.error("Error occurs when login", e);
|
||||||
|
}
|
||||||
|
return allowed || super.isPermissive(mappedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {
|
||||||
|
String jwtToken = getAuthzHeader(servletRequest);
|
||||||
|
if(StringUtils.isNotBlank(jwtToken)&&!JwtUtils.isTokenExpired(jwtToken))
|
||||||
|
return new JWTToken(jwtToken);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
|
||||||
|
HttpServletResponse httpResponse = WebUtils.toHttp(servletResponse);
|
||||||
|
httpResponse.setCharacterEncoding("UTF-8");
|
||||||
|
httpResponse.setContentType("application/json;charset=UTF-8");
|
||||||
|
httpResponse.setStatus(HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION);
|
||||||
|
fillCorsHeader(WebUtils.toHttp(servletRequest), httpResponse);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
|
||||||
|
HttpServletResponse httpResponse = WebUtils.toHttp(response);
|
||||||
|
String newToken = null;
|
||||||
|
if(token instanceof JWTToken){
|
||||||
|
JWTToken jwtToken = (JWTToken)token;
|
||||||
|
UserDto user = (UserDto) subject.getPrincipal();
|
||||||
|
boolean shouldRefresh = shouldTokenRefresh(JwtUtils.getIssuedAt(jwtToken.getToken()));
|
||||||
|
if(shouldRefresh) {
|
||||||
|
newToken = userService.generateJwtToken(user.getUsername());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(StringUtils.isNotBlank(newToken))
|
||||||
|
httpResponse.setHeader("x-auth-token", newToken);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
|
||||||
|
log.error("Validate token fail, token:{}, error:{}", token.toString(), e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getAuthzHeader(ServletRequest request) {
|
||||||
|
HttpServletRequest httpRequest = WebUtils.toHttp(request);
|
||||||
|
String header = httpRequest.getHeader("x-auth-token");
|
||||||
|
return StringUtils.removeStart(header, "Bearer ");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean shouldTokenRefresh(Date issueAt){
|
||||||
|
LocalDateTime issueTime = LocalDateTime.ofInstant(issueAt.toInstant(), ZoneId.systemDefault());
|
||||||
|
return LocalDateTime.now().minusSeconds(tokenRefreshInterval).isAfter(issueTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fillCorsHeader(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse){
|
||||||
|
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
|
||||||
|
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,HEAD");
|
||||||
|
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package com.github.demo.service;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.shiro.crypto.hash.Sha256Hash;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.github.demo.configuration.JwtUtils;
|
||||||
|
import com.github.demo.dto.UserDto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息接口
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class UserService {
|
||||||
|
|
||||||
|
private static final String encryptSalt = "F12839WhsnnEV$#23b";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StringRedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存user登录信息,返回token
|
||||||
|
* @param userDto
|
||||||
|
*/
|
||||||
|
public String generateJwtToken(String username) {
|
||||||
|
String salt = "12345";//JwtUtils.generateSalt();
|
||||||
|
/**
|
||||||
|
* @todo 将salt保存到数据库或者缓存中
|
||||||
|
* redisTemplate.opsForValue().set("token:"+username, salt, 3600, TimeUnit.SECONDS);
|
||||||
|
*/
|
||||||
|
return JwtUtils.sign(username, salt, 3600); //生成jwt token,设置过期时间为1小时
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取上次token生成时的salt值和登录用户信息
|
||||||
|
* @param username
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public UserDto getJwtTokenInfo(String username) {
|
||||||
|
String salt = "12345";
|
||||||
|
/**
|
||||||
|
* @todo 从数据库或者缓存中取出jwt token生成时用的salt
|
||||||
|
* salt = redisTemplate.opsForValue().get("token:"+username);
|
||||||
|
*/
|
||||||
|
UserDto user = getUserInfo(username);
|
||||||
|
user.setSalt(salt);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除token信息
|
||||||
|
* @param userName 登录用户名
|
||||||
|
* @param terminal 登录终端
|
||||||
|
*/
|
||||||
|
public void deleteLoginInfo(String username) {
|
||||||
|
/**
|
||||||
|
* @todo 删除数据库或者缓存中保存的salt
|
||||||
|
* redisTemplate.delete("token:"+username);
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库中保存的用户信息,主要是加密后的密码
|
||||||
|
* @param userName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public UserDto getUserInfo(String userName) {
|
||||||
|
UserDto user = new UserDto();
|
||||||
|
user.setUserId(1L);
|
||||||
|
user.setUsername("admin");
|
||||||
|
user.setEncryptPwd(new Sha256Hash("123456", encryptSalt).toHex());
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户角色列表,强烈建议从缓存中获取
|
||||||
|
* @param userId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getUserRoles(Long userId){
|
||||||
|
return Arrays.asList("admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
pom.xml
1
pom.xml
|
@ -25,6 +25,7 @@
|
||||||
<module>SpringBootShiroAuthorization</module>
|
<module>SpringBootShiroAuthorization</module>
|
||||||
<module>SpringBootException</module>
|
<module>SpringBootException</module>
|
||||||
<module>SpringBootMybatis</module>
|
<module>SpringBootMybatis</module>
|
||||||
|
<module>SpringBootShiroJWT</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
|
Loading…
Reference in New Issue