一、shiro和SpringSecurity的对比
目前比较流行的安全管理框架是shiro和SpringSecurity
据我了解所知shiro简单易用
SpringSecurity相对比较复杂,可扩展性好
二、shiro的作用
1、验证用户身份
2.用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。判断用户是否被授予完成某个操作的权限
3.在非 web 或 EJB 容器的环境下可以任意使用Session API
4、可以响应认证、访问控制,或者 Session 生命周期中发生的事件5、可将一个或以上用户安全数据源数据组合成一个复合的用户 “view”(视图)
6、支持单点登录(SSO)功能
7、支持提供“Remember Me”服务,获取用户关联信息而无需登录
三、shiro的架构
Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。
四、代码如下
1.pom文件中引入shiro依赖
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-thymeleaf
net.sourceforge.nekohtml
nekohtml
1.9.22
org.springframework.boot
spring-boot-starter-web
org.apache.shiro
shiro-spring
1.4.0
org.crazycake
shiro-redis
2.4.2.1-RELEASE
com.alibaba
fastjson
1.2.13
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
org.apache.commons
commons-lang3
3.6
``
2.在application.yaml文件中配置thymeleaf和redis
spring:
datasource:
url: jdbc:mysql://localhost:8801/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
#schema: database/import.sql
#sql-script-encoding: utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
#创建数据库表
jpa:
database: mysql
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
thymeleaf:
cache: false
mode: HTML
redis:
host: 127.0.0.1
port: 6379
database: 0
server:
port: 8087
3.在 shiroConfig类中借助redis配置会话管理、缓存管理和开启权限注解和url的权限和最大登录人数
package com.neo.config;
import org.apache.shiro.mgt.SecurityManager;//容易和java自带的包冲突
import javax.servlet.Filter;
import java.util.Properties;
@Configuration
public class ShiroConfig implements EnvironmentAware {
private String host;
private Integer port;
@Override
public void setEnvironment(Environment environment) {
host = environment.getProperty("spring.redis.host");
port = Integer.valueOf(environment.getProperty("spring.redis.port"));
System.out.println("host==="+host+"port==="+port);
}
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");//这里的是login的rest接口
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//自定义拦截器
Map filtersMap = new LinkedHashMap();
//限制同一帐号同时在线的个数。
filtersMap.put("kickout", kickoutSessionControlFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
//拦截器.
Map filterChainDefinitionMap = new LinkedHashMap();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//:这是一个坑呢,一不小心代码就不好使了;
//
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/kickout", "anon");
filterChainDefinitionMap.put("/**", "authc,kickout");
// filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
//设置凭证匹配器
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@Bean
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);
return redisManager;
}
@Bean
public RedisSessionDAO sessionDAO(){
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
//缓存管理器
@Bean
public RedisCacheManager cacheManager(){
RedisCacheManager cacheManager = new RedisCacheManager();
cacheManager.setRedisManager(redisManager());
return cacheManager;
}
@Bean
public DefaultWebSessionManager sessionManager(){
//跨域session共享
// DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
SessionManager sessionManager = new SessionManager();
sessionManager.setSessionDAO(sessionDAO());
return sessionManager;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
//有说法securityManager.setRealm放在最后,否则可能会出现无法调用权限认证方法
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* 开启shiro aop注解支持.(如@RequiresRoles,@RequiresPermissions)使用代理方式;所以需要开启代码支持;
*
* 使授权注解起作用不如不想配置可以在pom文件中加入
*
*org.springframework.boot
*spring-boot-starter-aop
*
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
// 限制同一账号登录同时登录人数控制
@Bean
public KickoutSessionControlFilter kickoutSessionControlFilter() {
KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
kickoutSessionControlFilter.setCacheManager(cacheManager());
kickoutSessionControlFilter.setSessionManager(sessionManager());
kickoutSessionControlFilter.setKickoutAfter(false);
kickoutSessionControlFilter.setMaxSession(1);
kickoutSessionControlFilter.setKickoutUrl("/kickout");//这里的是rest接口
return kickoutSessionControlFilter;
}
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver
createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException","403");
r.setExceptionMappings(mappings); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("ex"); // Default is "exception"
//r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
}
}
4.自定义realms实现身份认证和权限认证
package com.neo.config;
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private UserInfoService userInfoService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal();
for(SysRole role:userInfo.getRoleList()){
authorizationInfo.addRole(role.getRole());
for(SysPermission p:role.getPermissions()){
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
/*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
//获取用户的输入的账号.
String username = (String)token.getPrincipal();
System.out.println(token.getCredentials());
//通过username从数据库中查找 User对象,如果找到,没找到.
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
UserInfo userInfo = userInfoService.findByUsername(username);
System.out.println("----->>userInfo="+userInfo);
if(userInfo == null){
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassword(), //密码
ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
}
5.在Controller类中实现对登录、登出等请求接口指定跳转页面。
package com.neo.web;
@Controller
public class HomeController {
@Autowired
LoginService loginService;
@RequestMapping({"/","/index"})
public String index(){
return"/index";
}
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String toLogin(Map map,HttpServletRequest request)
{
loginService.logout();
return "/login";
}
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(Map map,HttpServletRequest request) throws Exception{
System.out.println("login()");
String userName = request.getParameter("userName");
String password = request.getParameter("password");
LoginResult loginResult = loginService.login(userName,password);
if(loginResult.isLogin())
{
return "/index";
}
else {
map.put("msg",loginResult.getResult());
map.put("userName",userName);
return "/login";
}
}
//被踢出后跳转的页面
@RequestMapping(value = "/kickout", method = RequestMethod.GET)
public String kickOut() {
return "/kickOut2";
}
@RequestMapping("/403")
public String unauthorizedRole(){
System.out.println("------没有权限-------");
return "403";
}
}
package com.neo.web;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/userInfo")
public class UserInfoController {
/**
* 用户查询.
* @return
*/
@RequestMapping("/userList")
@RequiresPermissions("userInfo:view")//权限管理;
public String userInfo(){
return "userInfo";
}
/**
* 用户添加;
* @return
*/
@RequestMapping("/userAdd")
@RequiresPermissions("userInfo:add")//权限管理;
public String userInfoAdd(){
return "userInfoAdd";
}
/**
* 用户删除;
* @return
*/
@RequestMapping("/userDel")
@RequiresPermissions("userInfo:del")//权限管理;
public String userDel(){
return "userInfoDel";
}
}
github源码地址:https://github.com/nvhanzijiuba/springboot-examples.git
注意:此项目先启动Application类来生成数据库表,之后再导入import.sql语句来插入数据。