springboot整合shiro

shiro能夠說是簡單卻又功能強大的框架了,比起springSecurity 更加輕巧易用,在實際開發中用到也多,本人所在的公司也是用這個來作認證受權的,下面就介紹一下它的用法html

1,項目目錄結構

2,pom.xml

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.RELEASE</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.0</version>
		</dependency>

		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
		    <groupId>com.github.theborakompanioni</groupId>
		    <artifactId>thymeleaf-extras-shiro</artifactId>
		    <version>2.0.0</version>
		</dependency>
				
	</dependencies>

3,application.properties

spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.url=jdbc:mysql://localhost:3306/cloud2?useUnicode=true&characterEncoding=UTF8&useServerPrepStmts=true&prepStmtCacheSqlLimit=256&cachePrepStmts=true&prepStmtCacheSize=256&rewriteBatchedStatements=true

mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath*:com/example/demo/**/dao/xml/*.xml

logging.level.com.example.demo=DEBUG


spring.thymeleaf.cache=false

4,mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<settings>
		<setting name="cacheEnabled" value="true" />
		<setting name="lazyLoadingEnabled" value="true" />
		<setting name="mapUnderscoreToCamelCase" value="true" />
		<setting name="jdbcTypeForNull" value="NULL" />
		<setting name="useActualParamName" value="false" />
	</settings>
</configuration>

5,配置路徑映射

@Configuration
public class MvcConfig implements WebMvcConfigurer{

	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/").setViewName("index");
		registry.addViewController("/index").setViewName("index");
		registry.addViewController("/welcome").setViewName("welcome");
		registry.addViewController("/user").setViewName("user");
		registry.addViewController("/admin").setViewName("admin");
	}
}

6,shiro配置

①,shiro對特定路徑的攔截

public class ShiroFilterMapFactory {

	public static Map<String, String> shiroFilterMap() {
//		設置路徑映射,注意這裏要用LinkedHashMap 保證有序
		LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
		filterChainDefinitionMap.put("/welcome", "anon");
		filterChainDefinitionMap.put("/login", "anon");
		filterChainDefinitionMap.put("/logout", "logout");
		filterChainDefinitionMap.put("/user", "roles[user]");
		filterChainDefinitionMap.put("/admin", "roles[admin]");
		filterChainDefinitionMap.put("/**", "user");//user容許 記住我和受權用戶 訪問,但在進行下單和支付時建議使用authc

		return filterChainDefinitionMap;
	}
}

②,shiro全局文件配置

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.example.demo.shiro.realm.ShiroRealm;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

@Configuration
public class ShiroConfig {

//	這是shiro的大管家,至關於mybatis裏的SqlSessionFactoryBean
	@Bean
	public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setLoginUrl("/login");
		shiroFilterFactoryBean.setSuccessUrl("/index");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(ShiroFilterMapFactory.shiroFilterMap());

		shiroFilterFactoryBean.setSecurityManager(securityManager);

		return shiroFilterFactoryBean;
	}
//  web應用管理配置
	@Bean
	public DefaultWebSecurityManager securityManager(Realm shiroRealm,CacheManager cacheManager,RememberMeManager manager) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setCacheManager(cacheManager);
		securityManager.setRememberMeManager(manager);//記住Cookie
		securityManager.setRealm(shiroRealm);
		return securityManager;
	}
//  加密算法
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		hashedCredentialsMatcher.setHashAlgorithmName("MD5");//採用MD5 進行加密
		hashedCredentialsMatcher.setHashIterations(1);//加密次數
		return hashedCredentialsMatcher;
	}
//	記住個人配置
	@Bean
	public RememberMeManager rememberMeManager() {
		Cookie cookie = new SimpleCookie("rememberMe");
        cookie.setHttpOnly(true);//經過js腳本將沒法讀取到cookie信息
        cookie.setMaxAge(60 * 60 * 24);//cookie保存一天
		CookieRememberMeManager manager=new CookieRememberMeManager();
		manager.setCookie(cookie);
		return manager;
	}
//  緩存配置
	@Bean
	public CacheManager cacheManager() {
		MemoryConstrainedCacheManager cacheManager=new MemoryConstrainedCacheManager();//使用內存緩存
		return cacheManager;
	}
//	配置realm,用於認證和受權
	@Bean
	public AuthorizingRealm shiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
		ShiroRealm shiroRealm = new ShiroRealm();
//		校驗密碼用到的算法
		shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
		return shiroRealm;
	}
//	啓用shiro方言,這樣能在頁面上使用shiro標籤
	@Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
/**
     * 啓用shiro註解
     *
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

③,shiroRealm的編寫

import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import com.example.demo.user.bean.User;
import com.example.demo.user.dao.UserDao;
//繼承AuthorizingRealm,重寫認證和受權方法
public class ShiroRealm extends AuthorizingRealm {
	
	@Autowired
	private UserDao userDao;

	/**
	 * 受權方法,若是不設置緩存管理的話,須要訪問須要必定的權限或角色的請求時會進入這個方法
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		User principal = (User) principals.getPrimaryPrincipal();
		Set<String> roles = new HashSet<>();
		roles.add("user");
		if("admin".equals(principal.getUsername())){
			roles.add("admin");
		}
		
		return new SimpleAuthorizationInfo(roles);
	}

	/**
	 * 認證方法
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		
		UsernamePasswordToken userToken=(UsernamePasswordToken) token;
		//根據登陸名查詢用戶,這裏不用校驗密碼,由於密碼的校驗是交給shiro來完成的
		User userInfo=userDao.findByUserName(userToken.getUsername());
		
		if(userInfo == null) {
			throw new IncorrectCredentialsException("用戶名或密碼不正確");
		}
		
		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
	                userInfo,//用戶
	                userInfo.getPassword(),//密碼
	                ByteSource.Util.bytes(userInfo.getUsername()),//鹽值用 ByteSource.Util.bytes 來生成
	                getName()//realm name
	        );
		return authenticationInfo;
	}
    public static void main(String[] args) {
		//算出鹽值
		String credentials="123";
		String salt="小蘇";
		String hashAlgorithmName="MD5";
		int hashIterations=1024;
		SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
		System.out.println(simpleHash);
		
	}
	
}

④,LoginController 的編寫

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.example.demo.user.bean.User;

@Controller
public class LoginController {

	private static Logger logger=LoggerFactory.getLogger(LoginController.class);
	
	@PostMapping("login")
	public ModelAndView login(User user,RedirectAttributes redirectAttributes,boolean rememberMe) {
		ModelAndView view =new ModelAndView();
		 String userName = user.getUsername();
		 Subject currentUser = SecurityUtils.getSubject();
		 if(!currentUser.isAuthenticated()) {
			 UsernamePasswordToken token =new UsernamePasswordToken(userName,user.getPassword());
			 try {
				 if(rememberMe) {
					 token.setRememberMe(true);
				 }
				 currentUser.login(token);
				 
				 
				 view.setViewName("redirect:/");
			 }catch (UnknownAccountException uae) {
		            logger.info("對用戶[" + userName + "]進行登陸驗證..驗證未經過,未知帳戶");
		            redirectAttributes.addFlashAttribute("message", "未知帳戶");
		        } catch (IncorrectCredentialsException ice) {
		            logger.info("對用戶[" + userName + "]進行登陸驗證..驗證未經過,錯誤的憑證");
		            redirectAttributes.addFlashAttribute("message", "用戶名或密碼不正確");
		        } catch (LockedAccountException lae) {
		            logger.info("對用戶[" + userName + "]進行登陸驗證..驗證未經過,帳戶已鎖定");
		            redirectAttributes.addFlashAttribute("message", "帳戶已鎖定");
		        } catch (ExcessiveAttemptsException eae) {
		            logger.info("對用戶[" + userName + "]進行登陸驗證..驗證未經過,錯誤次數過多");
		            redirectAttributes.addFlashAttribute("message", "用戶名或密碼錯誤次數過多");
		        } catch (AuthenticationException ae) {
		            //經過處理Shiro的運行時AuthenticationException就能夠控制用戶登陸失敗或密碼錯誤時的情景
		            logger.info("對用戶[" + userName + "]進行登陸驗證..驗證未經過,堆棧軌跡以下");
		            ae.printStackTrace();
		            redirectAttributes.addFlashAttribute("message", "用戶名或密碼不正確");
		        }
		 }
		 
		 view.setViewName("redirect:/login");
		 return view;
		 
	}
	
	@GetMapping("/login")
    public String login(HttpServletRequest request) {
        try {
            if ((null != SecurityUtils.getSubject() && SecurityUtils.getSubject().isAuthenticated()) || SecurityUtils.getSubject().isRemembered()) {
                return "redirect:/";
            } else {
            	logger.info("--進行登陸驗證..驗證開始");
                return "login";
            }
        } catch (Exception e) {
        		e.printStackTrace();
        }
        return "login";
    }
@RequestMapping("su")
	public ModelAndView su() {
		ModelAndView view =new ModelAndView("index");
		System.out.println("來到了su方法。。。");
		
		return view;
	}
	

	@RequiresRoles({"admin"})
	@RequestMapping("xiao")
	public ModelAndView xiao() {
		ModelAndView view =new ModelAndView("index");
		System.out.println("來到了xiao方法。。。");
		
		return view;
	}
}

7,登陸的實體

import java.io.Serializable;

public class User implements Serializable{

	private String username;
	private String password;
	private String salt;
// get/set
}

8,dao層實現

①,UserDao 代碼

import org.apache.ibatis.annotations.Mapper;

import com.example.demo.user.bean.User;

@Mapper
public interface UserDao {
	
	User findByUserName(String username);

}

②,UserMapper.xml 代碼

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.demo.user.dao.UserDao">

	<select id="findByUserName" resultType="com.example.demo.user.bean.User">
		select * from user where username=#{username}
	</select>

</mapper>

9,啓動類代碼

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

//開啓AspectJAutoProxy
@EnableAspectJAutoProxy
@SpringBootApplication
public class SpringbootShiroApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringbootShiroApplication.class, args);
	}
}

10,測試頁面

①,login.html

<html>
<head>
<meta charset="UTF-8"/>
<title>login</title>
</head>
<body>

<form action="/login" method="post">
	用戶名:<input type="text" name="username" /><br/>
	密碼:<input type="password" name="password" /><br/>
	<label>
		<input type="checkbox" name="rememberMe" />記住我
	</label><br/>
	
	<input type="submit" value="submit" />
</form>

</body>
</html>

②,index.html

<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8"/>
<title>login</title>
</head>
<body>
<shiro:guest>    
	<a th:href="@{/login}">登陸</a> <a>註冊</a>  
</shiro:guest>    

<shiro:user>    
	歡迎<shiro:principal property="username"/>  &nbsp;
	<a th:href="@{/logout}">退出</a>
</shiro:user>  


index page..

<shiro:hasRole="user">
	有用戶角色哦、、、、
</shiro:hasRole>
<shiro:hasRole="admin">
	有admin角色哦、、、、
</shiro:hasRole>
<br>
<a th:href="@{/su}">蘇方法</a>

<br>
<a th:href="@{/xiao}">小方法</a>

<br/>

<div shiro:hasRole="user">
	===有用戶角色哦、、、、
</div>
<br/>
<shiro:authenticated>    
	w jinguo renzl...
</shiro:authenticated> 


<br/>
<div shiro:hasRole="admin">
	===有admin角色哦、、、、
</div>
<div shiro:lacksRole="admin">
	===mei有admin角色哦、、、、
</div>

<br/>

<script th:src="@{/js/jquery-2.2.3.min.js}"></script>
<script>

$(function(){

	console.log('<shiro:principal property="username"/>');
})
	
</script>
</body>
</html>

以上爲shiro標籤的使用,比較簡單,至於shiro有那些能使用的標籤,能夠參考,Shiro-Dialect.xml java

③,user.html

<html>
<head>
<meta charset="UTF-8"/>
<title>login</title>
</head>
<body>

user page..

</body>
</html>

admin.html和welcome.html 與其類似,這裏就不寫了mysql

④,測試

最後便能進行登陸測試,很簡單吧,快來試試吧,體驗shiro的強大功能jquery

11,彩蛋

①,shiro對密碼校驗原理

1,由於UsernamePasswordToken 保存着表單信息,全部咱們能夠在其getPassword 方法打一個斷點git

2,進行登陸,走到了這個斷點,往前面觀察,就是在這裏進行密碼的比對github

3,在return equals 所在行打一個斷點,其中表單登陸密碼通過加密後獲得結果保存在tokenHashedCredentials中。web

4,accountCredentials 爲咱們認證時放入的從數據庫讀取的密碼算法

5,而後進行比對spring

相關文章
相關標籤/搜索