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");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue