authorization

master
bseayin 2019-09-08 10:31:48 +08:00
parent e21234ed3f
commit a341faaebb
20 changed files with 1246 additions and 0 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 {
/**
* tokensecret
* @return token
*/
public static Date getIssuedAt(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getIssuedAt();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* tokensecret
* @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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
};
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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"));
}
}

View File

@ -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;
/**
* usertoken
* @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小时
}
/**
* tokensalt
* @param username
* @return
*/
public UserDto getJwtTokenInfo(String username) {
String salt = "12345";
/**
* @todo jwt tokensalt
* 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");
}
}

View File

@ -25,6 +25,7 @@
<module>SpringBootShiroAuthorization</module>
<module>SpringBootException</module>
<module>SpringBootMybatis</module>
<module>SpringBootShiroJWT</module>
</modules>
<repositories>