- Maven依赖及配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 数据库连接-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- spring session-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- shiro spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
<!-- <scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/libs/shiro-redis-3.2.3.jar</systemPath> -->
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>
<!-- lombok idea需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
</dependencies>
###数据库连接配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.url=jdbc:mysql:///jpa
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
###redis连接配置
spring.redis.database=5
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
#连接池最大阻塞时间,-1表示不限制
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
- 项目结构
- 数据表设计
CREATE TABLE `sys_user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(100) NOT NULL DEFAULT '',
`pwd` VARCHAR(100) NOT NULL DEFAULT '',
`create_time` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `索引 2` (`username`)
)
COLLATE='utf8_general_ci';
CREATE TABLE `sys_role` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`desc` VARCHAR(50) NULL DEFAULT '0',
`create_time` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci';
CREATE TABLE `sys_user_role` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_id` BIGINT(20) NOT NULL,
`role_id` BIGINT(20) NOT NULL,
`create_time` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci';
CREATE TABLE `sys_resource` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '',
`parent_id` BIGINT NOT NULL DEFAULT 0,
`url` VARCHAR(250) NOT NULL DEFAULT '0',
`create_time` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci';
CREATE TABLE `sys_role_resource` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`role_id` BIGINT NOT NULL,
`resource_id` BIGINT NOT NULL,
`create_time` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci';
- 集成代码
package org.sang.config.shiro;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 权限框架
*/
@Configuration
public class ShiroConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password}")
private String redisPassword;
@Value("${spring.redis.database}")
private int redisDatabase;
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/security/logout", "anon");
filterChainDefinitionMap.put("/security/login", "anon");
filterChainDefinitionMap.put("/security/forget", "anon");
filterChainDefinitionMap.put("/offlineBatch/**", "anon");
filterChainDefinitionMap.put("/experimental/**", "anon");
filterChainDefinitionMap.put("/modelCall/**", "anon");
filterChainDefinitionMap.put("/realTime/**", "anon");
//filterChainDefinitionMap.put("/experimental/train/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setLoginUrl("/user/unAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("SHA-1");//散列算法:这里使用SHA-1算法;
hashedCredentialsMatcher.setHashIterations(1024);//散列的次数
return hashedCredentialsMatcher;
}
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
//myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
//Session会话数据存在Redis
securityManager.setSessionManager(sessionManager());
//用户权限记录Cache到Redis. Subject.logout()退出时触发缓存清理。如果没有调用退出接口,权限缓存到期自动消失(默认30分钟)
securityManager.setCacheManager(cacheManager());
return securityManager;
}
public SessionManager sessionManager() {
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setGlobalSessionTimeout(1800000); //session过期时间 优先级比server.servlet.session.timeout高
mySessionManager.setSessionDAO(redisSessionDAO());
// mySessionManager.setSessionValidationInterval(sessionValidationInterval);
// mySessionManager.setSessionValidationSchedulerEnabled(true);
return mySessionManager;
// DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// sessionManager.setSessionDAO(redisSessionDAO());
// return sessionManager;
}
/**
* RedisClusterManager
* @return
*/
/*public RedisClusterManager redisManager() {
RedisClusterManager redisClusterManager = new RedisClusterManager();
redisClusterManager.setHost(host);
return redisClusterManager;
}
@Bean("myRedisCacheManager")
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}*/
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(redisHost + ":" + redisPort);
redisManager.setPassword(redisPassword);
redisManager.setDatabase(redisDatabase);
return redisManager;
}
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
package org.sang.config.shiro;
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.apache.shiro.util.CollectionUtils;
import org.crazycake.shiro.RedisSessionDAO;
import org.sang.dao.SysUserDao;
import org.sang.model.SysUser;
import org.sang.service.SysResourceService;
import javax.annotation.Resource;
import java.util.List;
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private SysResourceService sysResourceService;
@Resource
private SysUserDao sysUserDao;
@Resource
private RedisSessionDAO redisSessionDAO;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
SysUser user = (SysUser) principals.getPrimaryPrincipal();
try {
List<String> permissionList = sysResourceService.getResourceByUserId(user.getId());
if (!CollectionUtils.isEmpty(permissionList)) {
for (String permission : permissionList) {
if (permission != null && !"".equals(permission)) {
String[] permissions = permission.split(",");
for (String result : permissions) {
authorizationInfo.addStringPermission(result);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return authorizationInfo;
}
/*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
//获取用户的输入的账号.
String username = (String) token.getPrincipal();
String pwd = new String((char[]) token.getCredentials());
SysUser user = sysUserDao.findByUsername(username);
if (user == null || !user.getPwd().equalsIgnoreCase(pwd)) {
throw new UnknownAccountException();
}
//盐值密码
// byte[] salt = HexUtils.fromHexString(user.getPwd().substring(0, 16));
// Session session = SecurityUtils.getSubject().getSession();
// singleLogin.singleLogin(username, pwd, user, salt, session.getId());
// SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
// user, //用户
// user.getPwd().substring(16), //密码
// ByteSource.Util.bytes(salt),//salt=username+salt
// getName() //realm name
// );
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //传入用户对象,权限检测时需要读取用户信息
user.getPwd(), //密码
getName() //realm name
);
return authenticationInfo;
}
}
package org.sang.config.shiro;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "mySessionId";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
package org.sang.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.sang.common.Response;
import org.sang.vo.UserLoginVo;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.Serializable;
@RestController
@RequestMapping("security")
public class SecurityController {
/**
* 用户登录
* @param userLoginVo
* @return
*/
@PostMapping("login")
public Response<String> login(@Validated @RequestBody UserLoginVo userLoginVo) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userLoginVo.getUsername(), userLoginVo.getPwd());
try {
subject.login(token);
Serializable serializable = subject.getSession().getId();
return new Response(true, null, serializable);
} catch (Exception e) {
e.printStackTrace();
return new Response(false, null, e.getMessage());
}
}
@RequestMapping("logout")
public Response logout() {
try {
Subject subject = SecurityUtils.getSubject();
if (!ObjectUtils.isEmpty(subject)) {
subject.logout();
}
return new Response(true, "退出成功", null);
} catch (Exception e) {
return new Response(false, "系统异常", null);
}
}
}
- Postman测试