Springboot(2.0)整合shiro
这个项目使用MD5算法加密,salt为username加随机数。
项目源码:github:https://github.com/Plumblumpb/springboot-2.0--shiro.git
使用idea创建springboot工程并导入相关包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</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.2</version>
</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>
<!--druid数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--pageHelper分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- shiro ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 包含支持UI模版(Velocity,FreeMarker,JasperReports), 邮件服务, 脚本服务(JRuby), 缓存Cache(EHCache),
任务计划Scheduling(uartz)。 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!--shiro的thymeleaf标签库-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
1.导入sql并创建实体类和所需方法
1.使用jpa创建实体类
具体参考博文
2.所需方法
UserRepository实体类
public interface UserRepository extends JpaRepository<User,Long>{
@Query("select t from User t where t.username = ?1")
User findByName(String userName);
}
RoleMapper类
public interface RoleMapper {
List<String> findRoles(String userName);
}
RoleMapper.xml
<select id="findRoles" resultType="string">
select role from sys_user u, sys_role r,sys_user_role ur
where u.username=#{userName}
and u.id=ur.user_id and r.id=ur.role_id
</select>
PermissionMapper实体类
public interface PermissionMapper {
List<String> findPermissions(String userName);
}
PermissionMapper.xml
<select id="findPermissions" resultType="string">
select permission from sys_user u, sys_role r, sys_permission p, sys_user_role ur, sys_role_permission rp where u.username=#{userName} and u.id=ur.user_id and r.id=ur.role_id and r.id=rp.role_id and p.id=rp.permission_id
</select>
2.配置shiro
2.1配置PasswordHelper用于密码加密
/**
* @Auther: cpb
* @Date: 2018/8/10 10:57
* @Description:
*/
public class PasswordHelper {
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
private String algorithmName = "md5";
private int hashIterations = 2;
public void setRandomNumberGenerator(RandomNumberGenerator randomNumberGenerator) {
this.randomNumberGenerator = randomNumberGenerator;
}
public void setAlgorithmName(String algorithmName) {
this.algorithmName = algorithmName;
}
public void setHashIterations(int hashIterations) {
this.hashIterations = hashIterations;
}
public void encryptPassword(User user) {
user.setSalt(randomNumberGenerator.nextBytes().toHex());
String newPassword = new SimpleHash(
algorithmName,//加密算法
user.getPassword(),//密码
ByteSource.Util.bytes(user.getCredentialsSalt()),//盐值 username+salt
hashIterations//hash次数
).toHex();
user.setPassword(newPassword);
}
}
2.2配置UserRealm的验证和授权
/**
* @Auther: cpb
* @Date: 2018/8/10 10:36
* @Description:
*/
public class UserRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
User user = (User) principals.getPrimaryPrincipal();
List<String> roleList= userService.findRoles(user.getUsername());
List<String> permissionList= userService.findPermissions(user.getUsername());
System.out.println(permissionList);
Set<String> role = new HashSet<>();
Set<String> permission = new HashSet<>(permissionList.size());
role.addAll(roleList);
permission.addAll(permissionList);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(role);
authorizationInfo.setStringPermissions(permission);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户的输入的账号.
String username = (String)token.getPrincipal();
System.out.println(token.getCredentials());
//通过username从数据库中查找 User对象,如果找到,没找到.
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
User user = userService.findByName(username);
if(user == null) {
throw new UnknownAccountException();//没找到帐号
}
if(Boolean.TRUE.equals(user.getLocked())) {
throw new LockedAccountException(); //帐号锁定
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //用户名
user.getPassword(), //密码
ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}
2.3配置ShiroConfig
**
* @Auther: cpb
* @Date: 2018/8/10 11:32
* @Description:
*/
@Configuration
public class ShiroConfig {
//设置shiro拦截器
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/sys/login", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
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 UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
/*启动shiro在ioc容器中的注解,只有在使用
* 开启 Shiro 的注解功能 (如 @RequiresRoles,@RequiresPermissions),需借助 SpringAOP 扫描使 用 Shiro 注 解 的 类 , 并 在 必 要 时 进 行 安 全 逻 辑 验 证 , 需 要 配 置 两 个 bean(DefaultAdvisorAutoProxyCreator(可选 ) 和 AuthorizationAttributeSourceAdvisor) 实现此功 能。
Spring Boot系列 安全框架Apache Shiro基本功能*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* LifecycleBeanPostProcessor,这是个DestructionAwareBeanPostProcessor的子类,
* 负责org.apache.shiro.util.Initializable类型bean的生命周期的,初始化和销毁。
* 主要是AuthorizingRealm类的子类,以及EhCacheManager类。
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
//非必要
@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;
}
//thymeleaf的shiro标签库
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
3.配置界面
3.1配置Controller
@Controller
public class HomeController {
@RequestMapping({"/","/index"})
public String index(){
return "/index";
}
@RequestMapping("/login")
public String login(){
return "/login";
}
@RequestMapping("/sys/login")
@ResponseBody
public static R login(@RequestParam String username, String password) throws IOException {
System.out.println(username);
try {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
} catch (UnknownAccountException e) {
return R.error(e.getMessage());
} catch (IncorrectCredentialsException e) {
return R.error("账号或密码不正确");
} catch (LockedAccountException e) {
return R.error("账号已被锁定,请联系管理员");
} catch (AuthenticationException e) {
return R.error("账户验证失败");
}
return R.ok();
}
@RequestMapping("/403")
public String unauthorizedRole(){
System.out.println("------没有权限-------");
return "403";
}
/**
* 退出
*/
@RequestMapping(value = "logout", method = RequestMethod.GET)
public String logout() {
SecurityUtils.getSubject().logout();
return "redirect:login.html";
}
}
3.2界面
登录界面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Login</title>
<script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
</head>
<body>
<div id="login" v-cloak>
<div v-if="error">
<h4>{{errorMsg}}</h4>
</div>
<p>账号:<input type="text" v-model="username" /></p>
<p>密码:<input type="text" v-model="password" /></p>
<!--<p><input type="submit" value="登录" /></p>-->
<button type="button" @click="login">登录</button>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#login',
data: {
username: '',
password: '',
error:false,
errorMsg: ''
},
methods: {
login: function (event) {
var data = "username=" + vm.username + "&password=" + vm.password;
$.ajax({
type: "POST",
url: "/sys/login",
data: data,
dataType: "json",
success: function (result) {
if (result.code == 0) {//登录成功
window.location.href = 'index';
} else {
vm.error = true;
vm.errorMsg = result.msg;
}
}
});
}
},
created: function () {
}
});
</script>
</body>
</html>
登录成功界面
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" >
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h1>index</h1>
<shiro:authenticated>
用户<shiro:principal property="username"/>已身份验证通过
</shiro:authenticated>
<a href="/logout">退出</a>
<shiro:hasRole name="admin">
用户[<shiro:principal/>]拥有角色admin<br/>
</shiro:hasRole>
<shiro:hasPermission name="user:create">
用户[<shiro:principal/>]拥有权限userCreate<br/>
</shiro:hasPermission>
</body>
</html>
##4.运行测试
##5.注意事项
如果不能使用shiro标签请注意三点
1.使用引入jar包
2.配置shiroConfig
3.html是否引入标签