一、概念
Shiro是一个Java安全框架,可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。
Subject:即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Subject 不仅仅代表某个用户,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。
SecurityManager:管理所有Subject,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),可以看成数据库。
认证流程:
使用流程:
1. 自定义realm
3.提交认证,将携带的信息储存在 UsernamePasswordToken中
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username,password);
4.获取subject
Subject subject = SecurityUtils.getSubject();
5.登录
subject.login(usernamePasswordToken);
二、代码
代码层级
1.pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>springboot-shiro-10095</artifactId> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</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-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.44</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.4</version> </dependency> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
2.application.yml
server: port: 10095 spring: application: name: shiro # zipkin: # base-url: http://127.0.0.1:10092 #eureka #eureka: # client: # service-url: # defaultZone: http://localhost:10090/eureka/ datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=UTF-8 username: root password: root type: com.alibaba.druid.pool.DruidDataSource jpa: show-sql: true hibernate: ddl-auto: update http: encoding: charset: utf-8 enabled: true
3.关于shiro的表5张,利用JPA自动生成
(1)User 用户表
/** * All rights Reserved * @Title: User.java * @Package com.yanwu.www.entity * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:05:58 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.entity; import java.util.List; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * @ClassName: User * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:05:58 * * @Copyright: 2018 */ @Entity(name="user") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private String password; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
(2)Role 角色表
/** * All rights Reserved * @Title: Role.java * @Package com.yanwu.www.entity * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:07:43 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * @ClassName: Role * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:07:43 * * @Copyright: 2018 */ @Entity public class Role { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String roleName; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } }
(3)Permission 权限表
/** * All rights Reserved * @Title: Permission.java * @Package com.yanwu.www.entity * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:12:42 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * @ClassName: Permission * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:12:42 * * @Copyright: 2018 */ @Entity public class Permission { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String permission; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getPermission() { return permission; } public void setPermission(String permission) { this.permission = permission; } }
(4)UserRole 用户和角色关联
/** * All rights Reserved * @Title: UserRole.java * @Package com.yanwu.www.entity * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:15:24 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * @ClassName: UserRole * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:15:24 * * @Copyright: 2018 */ @Entity public class UserRole { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String userId; private String roleId; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getRoleId() { return roleId; } public void setRoleId(String roleId) { this.roleId = roleId; } }
(5)RolePermission 角色和权限关联
/** * All rights Reserved * @Title: RolePermission.java * @Package com.yanwu.www.entity * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:17:02 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * @ClassName: RolePermission * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:17:02 * * @Copyright: 2018 */ @Entity public class RolePermission { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String roleId; private String permissionId; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getRoleId() { return roleId; } public void setRoleId(String roleId) { this.roleId = roleId; } public String getPermissionId() { return permissionId; } public void setPermissionId(String permissionId) { this.permissionId = permissionId; } }
4.自定义realm(需要继承AuthorizingRealm)
/** * All rights Reserved * @Title: UserRealm.java * @Package com.yanwu.www.config * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:48:54 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.config; import java.util.List; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; 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 org.springframework.stereotype.Component; import com.yanwu.www.entity.Permission; import com.yanwu.www.entity.Role; import com.yanwu.www.entity.User; import com.yanwu.www.repository.PermissionRipository; import com.yanwu.www.repository.RoleRipository; import com.yanwu.www.repository.UserRipository; /** * @ClassName: UserRealm * @Description: 自定义realm * @author: harvey * @date: 2018年8月30日 下午2:48:54 * * @Copyright: 2018 */ @Component public class MyShiroRealm extends AuthorizingRealm{ @Autowired private UserRipository userRipository; @Autowired private RoleRipository roleRipository; @Autowired private PermissionRipository permissionRipository; /** * <p>Title: doGetAuthorizationInfo</p> * <p>Description: </p> * @param arg0 * @return * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection) */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取登录用户名 String username= (String) principalCollection.getPrimaryPrincipal(); //查询用户名称 User user = userRipository.findByName(username); //添加角色和权限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); List<Role> roles = roleRipository.findRolesByUserId(user.getId().toString()); for(Role role : roles){ // 添加角色 simpleAuthorizationInfo.addRole(role.getRoleName()); for(Permission permission : permissionRipository.findPermissionsByRoleId(role.getId().toString())){ // 添加权限 simpleAuthorizationInfo.addStringPermission(permission.getPermission()); } } return simpleAuthorizationInfo; } /** * <p>Title: doGetAuthenticationInfo</p> * <p>Description: </p> * @param arg0 * @return * @throws AuthenticationException * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 1.把AuthenticationToken转为UsernamePasswordToken对象 UsernamePasswordToken upToken = (UsernamePasswordToken) token; // 2.从UsernamePasswordToken获取username String userName = upToken.getUsername(); // 3.调用数据库方法,从数据库中查询username对应的用户记录 System.out.println("从数据库中获取信息userName:" + userName + "所对应信息"); User user = userRipository.findByName(userName); // 4.若用户不存在,则可以抛出异常 if (null == user) { throw new UnknownAccountException("用户不存在"); } // 5.根据用户情况,来构建AuthenticationInfo对象并返回,通常使用的实现类是SimpleAuthenticationInfo // 1)principal,用户名,认证实体,可以是实体,也可以是数据表对应的实体类对象 Object principal = userName; // 2)credential,密码,明文密码,即构建UsernamePasswordToken对象时的密码 Object credentials = user.getPassword().toString(); // 3)realmName:当前realm对象的name,调用父类的getName即可 String realmName = getName(); // 4.盐值(避免同一密码加密时产生相同的字符串,一般是用用户名做盐值) ByteSource credentialsSalt = ByteSource.Util.bytes(userName); // 不加密 //SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName); // 加密 SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal,credentials, credentialsSalt, realmName); return info; } // 增加用户时可以使用SimpleHash产生加密后的密码字符串存入数据库,避免数据库的用户密码明文显示 public static void main(String[] args) { String algorithmName="MD5"; Object source="1234"; Object salt=ByteSource.Util.bytes("user2"); int hashIterations=1024; Object result=new SimpleHash(algorithmName, source, salt, hashIterations); System.out.println(result); } }
5.shiro配置类
/** * All rights Reserved * @Title: ShiroConfig.java * @Package com.yanwu.www.config * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:47:16 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.config; import java.util.HashMap; import java.util.Map; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @ClassName: ShiroConfig * @Description:shiro配置类 * @author: harvey * @date: 2018年8月30日 下午2:47:16 * * @Copyright: 2018 */ @Configuration public class ShiroConfig { /** * 密码校验规则HashedCredentialsMatcher * 这个类是为了对密码进行编码的 , * 防止密码在数据库里明码保存 , 当然在登陆认证的时候 , * 这个类也负责对form里输入的密码进行编码 * 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //指定加密方式为MD5 credentialsMatcher.setHashAlgorithmName("MD5"); //加密次数 credentialsMatcher.setHashIterations(1024); credentialsMatcher.setStoredCredentialsHexEncoded(true); return credentialsMatcher; } //将自己的验证方式加入容器 @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); // 设置加密方式 myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } //权限管理,配置主要是Realm的管理认证 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } //Filter工厂,设置对应的过滤条件和跳转条件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String,String> map = new HashMap<String, String>(); //anon表示可以匿名访问,authc表示需要认证 //登出 map.put("/logout","anon"); //对所有用户认证 map.put("/**","authc"); //登录 shiroFilterFactoryBean.setLoginUrl("/login"); //首页 shiroFilterFactoryBean.setSuccessUrl("/index"); //错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } //加入注解的使用,不加入这个注解不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
6.Repositoty
(1)BaseRepository
package com.yanwu.www.repository; import java.io.Serializable; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.PagingAndSortingRepository; /** * * @ClassName: BaseRepository * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:21:18 * * @param <T> * @param <I> * @Copyright: 2018 */ @NoRepositoryBean public interface BaseRepository<T,I extends Serializable> extends PagingAndSortingRepository<T,I>,JpaSpecificationExecutor<T>{ }
(2)UserRipository
/** * All rights Reserved * @Title: UserRipository.java * @Package com.yanwu.www.repository * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:20:19 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.repository; import org.springframework.stereotype.Repository; import com.yanwu.www.entity.User; /** * @ClassName: UserRipository * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:20:19 * * @Copyright: 2018 */ @Repository public interface UserRipository extends BaseRepository<User, Long>{ User findByName(String username); }
(3)RoleRipository
/** * All rights Reserved * @Title: UserRipository.java * @Package com.yanwu.www.repository * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:20:19 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.repository; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import com.yanwu.www.entity.Role; /** * @ClassName: UserRipository * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:20:19 * * @Copyright: 2018 */ @Repository public interface RoleRipository extends BaseRepository<Role, Long>{ @Query(value = "select distinct r.* from role r inner join role_permission rp on rp.role_id = r.id inner join user_role ur on ur.role_id = r.id where ur.user_id =?1" ,nativeQuery=true) List<Role> findRolesByUserId(String userId); }
(4)PermissionRipository
/** * All rights Reserved * @Title: UserRipository.java * @Package com.yanwu.www.repository * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:20:19 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.repository; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import com.yanwu.www.entity.Permission; /** * @ClassName: UserRipository * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:20:19 * * @Copyright: 2018 */ @Repository public interface PermissionRipository extends BaseRepository<Permission, Long>{ @Query(value = "select distinct p.* from permission p inner join role_permission rp on rp.permission_id = p.id inner join role r on r.id = rp.role_id where r.id =?1" ,nativeQuery=true) List<Permission> findPermissionsByRoleId(String roleId); }
7.service
(1)UserService
/** * All rights Reserved * @Title: UserService.java * @Package com.yanwu.www.service * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:25:11 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.service; /** * @ClassName: UserService * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:25:11 * * @Copyright: 2018 */ public interface UserService { /** * * @Title: login * @Description: 用户登录 * @param username * @param password * @return * @return: String */ String login(String username,String password); /** * * @Title: logout * @Description: 用户登出 * @return * @return: String */ String logout(); }
(2)UserServiceImpl
/** * All rights Reserved * @Title: UserServiceImpl.java * @Package com.yanwu.www.service * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午2:27:15 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.service; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; /** * @ClassName: UserServiceImpl * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午2:27:15 * * @Copyright: 2018 */ @Component public class UserServiceImpl implements UserService { /** * <p>Title: login</p> * <p>Description: </p> * @param username * @param password * @see com.yanwu.www.service.UserService#login(java.lang.String, java.lang.String) */ @Override public String login(String username, String password) { if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){ throw new UnknownAccountException("账号或者密码为空"); } UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username,password); Subject subject = SecurityUtils.getSubject(); try{ subject.login(usernamePasswordToken); // 设置闲置session时间 subject.getSession().setTimeout(5*1000); return "login ok"; }catch( AuthenticationException e){ System.out.println("=========="); System.out.println(e.getMessage()); } return "login failure"; } /** * <p>Title: logout</p> * <p>Description: </p> * @return * @see com.yanwu.www.service.UserService#logout() */ @Override public String logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); return "logout ok"; } }
8.controller
/** * All rights Reserved * @Title: UserController.java * @Package com.yanwu.www.controller * @Description: TODO(用一句话描述该文件做什么) * @author: harvey * @date: 2018年8月30日 下午3:31:43 * @version V1.0 * @Copyright: 2018 */ package com.yanwu.www.controller; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.yanwu.www.service.UserService; /** * @ClassName: UserController * @Description:TODO(这里用一句话描述这个类的作用) * @author: harvey * @date: 2018年8月30日 下午3:31:43 * * @Copyright: 2018 */ @RestController public class UserController { @Autowired private UserService userService; //登录 @RequestMapping(value = "/login") public String login(String username,String password){ String status = userService.login(username, password); return status; } //首页 @RequestMapping(value = "/index") public String index(){ return "index"; } //登出 @RequestMapping(value = "/logout") public String logout(){ userService.logout(); return "logout"; } //错误页面展示 @RequestMapping(value = "/error",method = RequestMethod.POST) public String error(){ return "error ok!"; } // 测试shiro注解 @RequestMapping(value = "/test",method = RequestMethod.GET) @RequiresRoles(value={"admin"}) public String test(){ return "test ok!"; } }
三、注意点
加密
本博客已经配置,需要在自定义realm,securityManager进行修改,如下配置如何获得加密后的字符串
// 增加用户时可以使用SimpleHash产生加密后的密码字符串存入数据库,避免数据库的用户密码明文显示 public static void main(String[] args) { String algorithmName="MD5"; Object source="1234"; // 加盐 Object salt=ByteSource.Util.bytes("user2"); int hashIterations=1024; Object result=new SimpleHash(algorithmName, source, salt, hashIterations); System.out.println(result); }