一.springboot与shiro整合(示例)
地址:
github地址:点击打开链接https://github.com/MeloFocus/focus
前端水平有限见谅
目标:
(1)用springboot整合shiro
(2)完成简单的登录功能
(3)对url进行权限控制,当前登录用户拥有此url的权限码时,才可以访问此url
1.pom文件
可以先从此网站拉项目及所需要的依赖 http://start.spring.io/
目前需要的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
2.设计表,逆向生成
user:用户表
role:角色表
user_role_r:用户和角色关系表,多对多
role_business:业务角色表
resource:资源表
role_resource_r:角色资源关系表,多对多
authority:操作码表
resource_authority:资源和操作码关系表,多对多
最后逆向生成实体类,本博客另有一篇记录逆向生成
3.使用java配置,配置shiro
ShiroFilterFactoryBean:是个拦截器,在请求进入控制层前将其拦截,需要将安全管理器SecurityManager注入其中
SecurityManager:安全管理器,需要将自定义realm注入其中,以后还可以将缓存、remeberme等注入其中
自定义reaml:认证授权会执行它,需要自己写
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
* Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
//访问的是后端url地址为 /login的接口
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/ajaxLogin", "anon");
filterChainDefinitionMap.put("/userlogin", "anon");
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//配置某个url需要某个权限码
filterChainDefinitionMap.put("/hello", "perms[how_are_you]");
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/", "user");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myShiroRealm());
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
}
4.自定义realm
MyShiroRealm继承 AuthorizingRealm,重写doGetAuthorizationInfo授权方法,和doGetAuthenticationInfo认证方法
public class MyShiroRealm extends AuthorizingRealm{
Boolean cachingEnabled=true;
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
simpleAuthorInfo.addStringPermission("how_are_you");//给当前用户授权url为hello的权限码
System.out.println("经试验:并不是每次调用接口就会执行,而是调用需要操作码(permission)的接口就会执行");
return simpleAuthorInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
//获取基于用户名和密码的令牌
//实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String account = token.getUsername();
User user = userService.selectByAccount(account);//根据登陆名account从库中查询user对象
if(user==null){throw new AuthenticationException("用户不存在");}
//进行认证,将正确数据给shiro处理
//密码不用自己比对,AuthenticationInfo认证信息对象,一个接口,new他的实现类对象SimpleAuthenticationInfo
/* 第一个参数随便放,可以放user对象,程序可在任意位置获取 放入的对象
* 第二个参数必须放密码,
* 第三个参数放 当前realm的名字,因为可能有多个realm*/
AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
//AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user,user.getPassword(),new MySimpleByteSource(account), this.getName());
//清之前的授权信息
super.clearCachedAuthorizationInfo(authcInfo.getPrincipals());
SecurityUtils.getSubject().getSession().setAttribute("login", user);
return authcInfo;//返回给安全管理器,securityManager,由securityManager比对数据库查询出的密码和页面提交的密码
//如果有问题,向上抛异常,一直抛到控制器
}
}
5.页面
login页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>登录</title>
</head>
<body>
<form action="userlogin" method="post" >
<input type="text" name="loginName">请输入登录名</input>
<input type="password" name="password">请输入密码</input>
<input type="submit"></input>
<div th:text="${message}"></div>
</form>
</body>
</html>
index页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>首页</title>
</head>
<body>
<div th:text="'欢迎你'+${session.login.loginName}">这是谁</div>
<a th:href="@{/logout}">退出登录</a>
</body>
</html>
6.controller
@Controller
public class LoginController {
@RequestMapping(value="/userlogin")
public String userLogin(Model model,User user,HttpServletResponse response){
if(user==null){return "login";}
String account=user.getLoginName();
String password=user.getPassword();
UsernamePasswordToken token = new UsernamePasswordToken(account,password,false);
Subject currentUser = SecurityUtils.getSubject();
try {
currentUser.login(token);
//此步将 调用realm的认证方法
} catch(IncorrectCredentialsException e){
//这最好把 所有的 异常类型都背会
model.addAttribute("message", "密码错误");
return "login";
} catch (AuthenticationException e) {
model.addAttribute("message", "登录失败");
return "login";
}
return "index";
}
//配合shiro配置中的默认访问url
@RequestMapping(value="/login")
public String getLogin(HttpServletRequest request,Model model,HttpSession session,HttpServletResponse response){
return "login";
}
@RequestMapping(value="/hello")
public String hello(){
return "NewFile";
}
@RequestMapping(value="/")
public String index(){
System.out.println("访问了后端 / 请求");
return "login";
}
/**
* 退出
* @return
*/
@RequestMapping(value="logout",method =RequestMethod.GET)
public String logout(HttpServletRequest request){
//subject的实现类DelegatingSubject的logout方法,将本subject对象的session清空了
//即使session托管给了redis ,redis有很多个浏览器的session
//只要调用退出方法,此subject的、此浏览器的session就没了
try {
//退出
SecurityUtils.getSubject().logout();
} catch (Exception e) {
System.err.println(e.getMessage());
}
return "login";
}
@RequestMapping(value="403")
public String unAuth(){
return "403";
}
}
7.序列化异常
将 User类 实现序列化接口即可
8.登陆
9.对url进行权限控制
在ShiroFilterFactoryBean shirFilter中我们设置了当前登录用户必须具有 权限码是how_are_you的操作码,才能访问 url为hello的链接.
filterChainDefinitionMap.put("/hello", "perms[how_are_you]");
在自定义realm的授权方法中,给登录用户授权了操作码simpleAuthorInfo.addStringPermission("how_are_you");所有可以访问hello
如果没有授权,访问hello会到url为403的接口,这是在ShiroFilterFactoryBean shirFilter中配置的
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
10.问题
登录时调用认证方法后,会调用授权方法,以后每访问一个需要权限码的接口,都会调用自定义realm的授权方法,必然影响效率。后面会记录加入缓存,避免这个问题
更多推荐
所有评论(0)