Springboot与shiro整合(示例)

地址:

演示地址:http://47.98.153.30:8080     账号:melo 密码:12345678

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的授权方法,必然影响效率。后面会记录加入缓存,避免这个问题

 原文地址:点击访问原文

正文到此结束
本文目录