本文共 22170 字,大约阅读时间需要 73 分钟。
目录
shiro 介绍
shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。spring中有spring security (原名Acegi),是一个权限框架,它和spring依赖过于紧密,没有shiro使用简单。shiro不依赖于spring,shiro不仅可以实现 web应用的权限管理,还可以实现c/s系统,分布式系统权限管理,shiro属于轻量框架,越来越多企业项目开始使用shiro。
shiro 名词解释
subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。
securityManager:安全管理器,主体进行认证和授权都 是通过securityManager进行。它包含下面的认证器和授权器。
authenticator:认证器,主体进行认证最终通过authenticator进行的。
authorizer:授权器,主体进行授权最终通过authorizer进行的。
sessionManager:web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。可以实现单点登录。
SessionDao: 通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
cache Manager:缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
realm:域,领域,相当于数据源,通过realm存取认证、授权相关数据。(它的主要目的是与数据库打交道,查询数据库中的认证的信息(比如用户名和密码),查询授权的信息(比如权限的code等,所以这里可以理解为调用数据库查询一系列的信息,一般情况下在项目中采用自定义的realm,因为不同的业务需求不一样))
注意:在realm中存储授权和认证的逻辑。
cryptography:密码管理,提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。比如 md5散列算法
shiro 框架结构
认证过程
1、通过ini配置文件创建securityManager
2、调用subject.login方法主体提交认证,提交的token
3、securityManager进行认证,securityManager最终由ModularRealmAuthenticator进行认证。
4、ModularRealmAuthenticator调用IniRealm(给realm传入token) 去ini配置文件中查询用户信息
5、IniRealm根据输入的token(UsernamePasswordToken)从 shiro.ini查询用户信息,根据账号查询用户信息(账号和密码)
- 如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码)
- 如果查询不到,就给ModularRealmAuthenticator返回null
6、ModularRealmAuthenticator接收IniRealm返回Authentication认证信息
授权流程
- 如果返回的认证信息是null,ModularRealmAuthenticator抛出异常(org.apache.shiro.authc.UnknownAccountException)
- 如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在) 和 token中的密码 进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)
1、对subject进行授权,调用方法isPermitted("permission串")
2、SecurityManager执行授权,通过ModularRealmAuthorizer执行授权
3、ModularRealmAuthorizer执行realm(自定义的Realm)从数据库查询权限数据,调用realm的授权方法:doGetAuthorizationInfo
4、realm从数据库查询权限数据,返回ModularRealmAuthorizer
5、ModularRealmAuthorizer调用PermissionResolver进行权限串比对
6、如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。
与spring boot 整合(转)
目录结构
POM依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf nz.net.ultraq.thymeleaf thymeleaf-layout-dialect org.thymeleaf.extras thymeleaf-extras-java8time org.springframework.boot spring-boot-starter-tomcat compile org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java org.springframework.boot spring-boot-devtools true org.apache.shiro shiro-spring 1.4.0
Application.yml
server: servlet: context-path: / port: 80spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/crmData?characterEncoding=utf8&useSSL=false username: root password: root jpa: hibernate: ddl-auto: update naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #按字段名字建表 show-sql: true database: mysql database-platform: org.hibernate.dialect.MySQL5InnoDBDialect thymeleaf: cache: false messages: basename: myconfig
SQL脚本
INSERT INTO `user` (`userId`,`username`,`name`,`password`,`salt`,`state`)VALUES ('1', 'admin', '管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);INSERT INTO `syspermission` (`permissionId`,`available`,`permissionname`,`parentid`,`parentids`,`permission`,`resourcetype`,`url`)VALUES (1,0,'用户管理',0,'0/','user:view','menu','user/userList');INSERT INTO `syspermission` (`permissionId`,`available`,`permissionname`,`parentid`,`parentids`,`permission`,`resourcetype`,`url`)VALUES (2,0,'用户添加',1,'0/1','user:add','button','user/userAdd');INSERT INTO `syspermission` (`permissionId`,`available`,`permissionname`,`parentid`,`parentids`,`permission`,`resourcetype`,`url`)VALUES (3,0,'用户删除',1,'0/1','user:del','button','user/userDel');INSERT INTO `sysrole` (`roleid`,`available`,`description`,`role`) VALUES (1,0,'管理员','admin');INSERT INTO `sysrole` (`roleid`,`available`,`description`,`role`) VALUES (2,0,'VIP会员','vip');INSERT INTO `sysrole` (`roleid`,`available`,`description`,`role`) VALUES (3,1,'test','test');INSERT INTO `sysrolepermission` (`permissionid`,`roleid`) VALUES (1,1);INSERT INTO `sysrolepermission` (`permissionid`,`roleid`) VALUES (2,1);INSERT INTO `sysrolepermission` (`permissionid`,`roleid`) VALUES (3,2);INSERT INTO `sysuserrole` (`roleid`,`userId`) VALUES (1,1);
注意admin的密码是123456,这里保存的是加密后的密码,根据前面的设置,是md5,散列2次。
登录的时候shiro会根据配置自动给密码123456加密,然后与数据库里取出的密码比对。
注意先运行一遍程序,JPA生成数据库表后,手工执行sql脚本插入样本数据。
实体
实体有三个,根据规则会自动生成两个中间表,数据库实际上会生成5张表。
分别是User,SysRole,SysPermission,中间表按照@JoinTable来生成。
@Entitypublic class User { @Id @GenericGenerator(name="generator",strategy = "native") @GeneratedValue(generator = "generator") private Integer userId; @Column(nullable = false, unique = true) private String userName; //登录用户名 @Column(nullable = false) private String name;//名称(昵称或者真实姓名,根据实际情况定义) @Column(nullable = false) private String password; private String salt;//加密密码的盐 private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定. @ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据; @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "userId") }, inverseJoinColumns ={@JoinColumn(name = "roleId") }) private ListroleList;// 一个用户具有多个角色 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime createTime;//创建时间 @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate expiredDate;//过期日期 private String email; public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public LocalDateTime getCreateTime() { return createTime; } public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; } public LocalDate getExpiredDate() { return expiredDate; } public void setExpiredDate(LocalDate expiredDate) { this.expiredDate = expiredDate; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } 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; } public String getSalt() { return salt; } public void setSalt(String salt) { this.salt = salt; } public byte getState() { return state; } public void setState(byte state) { this.state = state; } public List getRoleList() { return roleList; } public void setRoleList(List roleList) { this.roleList = roleList; } /** * 密码盐. 重新对盐重新进行了定义,用户名+salt,这样就不容易被破解,可以采用多种方式定义加盐 * @return */ public String getCredentialsSalt(){ return this.userName+this.salt; }}
@Entitypublic class SysRole { @Id @GenericGenerator(name="generator",strategy = "native") @GeneratedValue(generator = "generator") private Integer roleId; // 编号 @Column(nullable = false, unique = true) private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的: private String description; // 角色描述,UI界面显示使用 private Boolean available = Boolean.TRUE; // 是否可用,如果不可用将不会添加给用户 //角色 -- 权限关系:多对多关系; @ManyToMany(fetch= FetchType.EAGER) @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")}) private Listpermissions; // 用户 - 角色关系定义; @ManyToMany @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="userId")}) private List users;// 一个角色对应多个用户 public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Boolean getAvailable() { return available; } public void setAvailable(Boolean available) { this.available = available; } public List getPermissions() { return permissions; } public void setPermissions(List permissions) { this.permissions = permissions; } public List getUsers() { return users; } public void setUsers(List users) { this.users = users; }}
@Entitypublic class SysPermission { @Id @GenericGenerator(name="generator",strategy = "native") @GeneratedValue(generator = "generator") private Integer permissionId;//主键. @Column(nullable = false) private String permissionName;//名称. @Column(columnDefinition="enum('menu','button')") private String resourceType;//资源类型,[menu|button] private String url;//资源路径. private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view private Long parentId; //父编号 private String parentIds; //父编号列表 private Boolean available = Boolean.TRUE; //角色 -- 权限关系:多对多关系; @ManyToMany @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")}) private Listroles; public Integer getPermissionId() { return permissionId; } public void setPermissionId(Integer permissionId) { this.permissionId = permissionId; } public String getPermissionName() { return permissionName; } public void setPermissionName(String permissionName) { this.permissionName = permissionName; } public String getResourceType() { return resourceType; } public void setResourceType(String resourceType) { this.resourceType = resourceType; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getPermission() { return permission; } public void setPermission(String permission) { this.permission = permission; } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } public String getParentIds() { return parentIds; } public void setParentIds(String parentIds) { this.parentIds = parentIds; } public Boolean getAvailable() { return available; } public void setAvailable(Boolean available) { this.available = available; } public List getRoles() { return roles; } public void setRoles(List roles) { this.roles = roles; }}
DAO,这里用JPA
public interface UserRepository extends JpaRepository{ User findByUserName(String userName);}
Service
public interface UserService { User findByUserName(String userName);}public interface LoginService { LoginResult login(String userName,String password); void logout();}
Service.impl
@Servicepublic class UserServiceImpl implements UserService { @Resource private UserRepository userRepository; @Override public User findByUserName(String userName) { return userRepository.findByUserName(userName); }}
//内部使用的一个model,根据需要扩展
public class LoginResult { private boolean isLogin = false; private String result; public boolean isLogin() { return isLogin; } public void setLogin(boolean login) { isLogin = login; } public String getResult() { return result; } public void setResult(String result) { this.result = result; }}
@Servicepublic class LoginServiceImpl implements LoginService { @Override public LoginResult login(String userName, String password) { LoginResult loginResult = new LoginResult(); if(userName==null || userName.isEmpty()) { loginResult.setLogin(false); loginResult.setResult("用户名为空"); return loginResult; } String msg=""; // 1、获取Subject实例对象 Subject currentUser = SecurityUtils.getSubject();// // 2、判断当前用户是否登录// if (currentUser.isAuthenticated() == false) {//// } // 3、将用户名和密码封装到UsernamePasswordToken UsernamePasswordToken token = new UsernamePasswordToken(userName, password); // 4、认证 try { currentUser.login(token);// 传到MyAuthorizingRealm类中的方法进行认证 Session session = currentUser.getSession(); session.setAttribute("userName", userName); loginResult.setLogin(true); return loginResult; //return "/index"; }catch (UnknownAccountException e) { e.printStackTrace(); msg = "UnknownAccountException -- > 账号不存在:"; } catch (IncorrectCredentialsException e) { msg = "IncorrectCredentialsException -- > 密码不正确:"; } catch (AuthenticationException e) { e.printStackTrace(); msg="用户验证失败"; } loginResult.setLogin(false); loginResult.setResult(msg); return loginResult; } @Override public void logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); }}
config,配置类
public class MyShiroRealm extends AuthorizingRealm { @Resource private UserService userService; //权限信息,包括角色以及权限 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //如果身份认证的时候没有传入User对象,这里只能取到userName //也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象 User user = (User)principals.getPrimaryPrincipal(); for(SysRole role:user.getRoleList()){ authorizationInfo.addRole(role.getRole()); for(SysPermission p:role.getPermissions()){ authorizationInfo.addStringPermission(p.getPermission()); } } return authorizationInfo; } /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); //获取用户的输入的账号. String userName = (String)token.getPrincipal(); System.out.println(token.getCredentials()); //通过username从数据库中查找 User对象. //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 User user = userService.findByUserName(userName); System.out.println("----->>user="+user); if(user == null){ return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user, //这里传入的是user对象,比对的是用户名,直接传入用户名也没错,但是在授权部分就需要自己重新从数据库里取权限 user.getPassword(), //密码 ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt getName() //realm name ); return authenticationInfo; }}
@Configurationpublic class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器. MapfilterChainDefinitionMap = new LinkedHashMap (); // 配置不会被拦截的链接 顺序判断,因为前端模板采用了thymeleaf,这里不能直接使用 ("/static/**", "anon")来配置匿名访问,必须配置到每个静态目录 filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/fonts/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/html/**", "anon"); //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); // :这是一个坑呢,一不小心代码就不好使了; // 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 MyShiroRealm myShiroRealm(){ MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean(name="simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理 mappings.setProperty("UnauthorizedException","/user/403"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("exception"); // Default is "exception" //r.setWarnLogCategory("example.MvcLogger"); // No default return r; }}
controller
@Controllerpublic class HomeController { @Resource private LoginService loginService; @RequestMapping({"/","/index"}) public String index(){ return"/index"; } @RequestMapping("/403") public String unauthorizedRole(){ System.out.println("------没有权限-------"); return "/user/403"; } @RequestMapping(value = "/login",method = RequestMethod.GET) public String toLogin(Mapmap,HttpServletRequest request) { loginService.logout(); return "/user/login"; } @RequestMapping(value = "/login",method = RequestMethod.POST) public String login(Map map,HttpServletRequest request) throws Exception{ System.out.println("login()"); String userName = request.getParameter("userName"); String password = request.getParameter("password"); LoginResult loginResult = loginService.login(userName,password); if(loginResult.isLogin()) { return "/index"; } else { map.put("msg",loginResult.getResult()); map.put("userName",userName); return "/user/login"; } } @RequestMapping("/logout") public String logOut(HttpSession session) { loginService.logout(); return "/user/login"; }}
@Controller@RequestMapping("/user")public class UserController { /** * 用户查询. * @return */ @RequestMapping("/userList") @RequiresPermissions("user:view")//权限管理; public String userInfo(){ return "userList"; } /** * 用户添加; * @return */ @RequestMapping("/userAdd") @RequiresPermissions("user:add")//权限管理; public String userInfoAdd(){ return "userAdd"; } /** * 用户删除; * @return */ @RequestMapping("/userDel") @RequiresPermissions("user:del")//权限管理; public String userDel(){ return "userDel"; }}
html
用户登录 用户登录
其他的html页面自己随便生成就可以。
参考:
转载地址:https://lemonstone.blog.csdn.net/article/details/85955417 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!