一 、介绍shiro框架
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。Shiro 主要分为来个部分就是认证和授权两部分
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。X
二、shiro的实现
整体的项目目录
1.添加jar包
org.apache.shiro shiro-spring 1.4.0
2.配置配置文件
spring.datasource.url= jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.type=com.alibaba.druid.pool.DruidDataSource #spring.datasource.schema=database/import.sql spring.thymeleaf.cache=false spring.thymeleaf.mode=LEGACYHTML5 spring.jpa.database=mysql #spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy spring.jpa.properties.hibernate.dialect: org.hibernate.dialect.MySQL5Dialect mybatis.mapper-locations=classpath:mapping/*.xml
3.自定义Realm 继承AuthorizingRealm 重写 AuthorizationInfo(授权) 和 AuthenticationInfo(认证)这两个
public class MyShiroRealm extends AuthorizingRealm { @Resource private UserInfoService userInfoService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); SysUser userInfo = (SysUser)principals.getPrimaryPrincipal(); Integer uid = userInfo.getUid(); Listr = userInfoService.getAllRole(uid); userInfo.setRoleList(r); for(SysRole role:userInfo.getRoleList()){ authorizationInfo.addRole(role.getRole()); List per = userInfoService.getAllPermission(role.getId()); role.setPermissions(per); 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分钟内不会重复执行该方法 SysUser userInfo = userInfoService.findByUsername(username); System.out.println("----->>userInfo="+userInfo.getPassword()); if(userInfo == null){ return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userInfo, //用户名 userInfo.getPassword(), //密码 ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt getName() //realm name ); return authenticationInfo; } }
4.配置过滤配置
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器. //必须是LinkedHashMap,因为要保证有序 MapfilterChainDefinitionMap = new LinkedHashMap (); // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/submitLogin","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","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; } }
5.实体类
user 用户类
@Entity public class SysUser implements Serializable { @Id @GeneratedValue private Integer uid; @Column(unique =true) private String username;//帐号 private String name;//名称(昵称或者真实姓名,不同系统不同定义) private String password; //密码; private String salt;//加密密码的盐 private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定. @ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据; @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") }) private ListroleList;// 一个用户具有多个角色
role 角色类
@Entity public class SysRole { @Id@GeneratedValue private Integer id; // 编号 private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的: private String description; // 角色描述,UI界面显示使用 private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户 //角色 -- 权限关系:多对多关系; @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="uid")}) private List userInfos;// 一个角色对应多个用户
permission 权限类
@Entity public class SysPermission implements Serializable { @Id@GeneratedValue private Integer id;//主键. private String name;//名称. @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.FALSE; @ManyToMany @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")}) private Listroles;
6.接下来编写controller
@RequestMapping(value="/submitLogin",method = RequestMethod.POST) @ResponseBody public MapsubmitLogin(String username,String password){ System.out.println(username+"-----"+password); String msg = ""; Map map = new HashMap<>(); try { UsernamePasswordToken token = token = new UsernamePasswordToken(username, password); char[] password1 = token.getPassword(); String s = password1.toString(); System.out.println("*******"+s); SecurityUtils.getSubject().login(token); token.setRememberMe(true); map.put("status",200); } catch (UnknownAccountException e) { msg = "UnknownAccountException -- > 账号不存在:"; map.put("status",400); } catch (IncorrectCredentialsException e){ msg = "IncorrectCredentialsException -- > 密码不正确:"; map.put("status",500); }catch (Exception exception){ msg = "else >> "+exception; System.out.println("else -- >" + exception); } return map; }