shiro springboot 整合
发布日期:2021-06-30 17:39:22 浏览次数:3 分类:技术文章

本文共 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 List
roleList;// 一个用户具有多个角色 @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 List
permissions; // 用户 - 角色关系定义; @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 List
roles; 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);      //拦截器.      Map
filterChainDefinitionMap = 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(Map
map,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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Git常用命令总结
下一篇:设计模式 过滤器模式

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2024年05月04日 17时31分37秒