0%

在springboot中结合shiro搭建权限管理,其中几个小细节的地方对新手不友好,搭建过程容易遇坑,记录一下。关键的地方也给注释了。

版本:springboot版本2.x,shiro1.4

一、依赖

<dependency>
   org.apache.shiro
   shiro-spring
   1.4.0

二、实体

实体有三个,根据规则会自动生成两个中间表,数据库实际上会生成5张表。

分别是User,SysRole,SysPermission,中间表按照@JoinTable来生成。

复制代码

@Entity public 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;
}
}

复制代码

复制代码

@Entity public 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<SysPermission> getPermissions() { return permissions;
} public void setPermissions(List<SysPermission> permissions) { this.permissions = permissions;
} public List<User> getUsers() { return users;
} public void setUsers(List<User> users) { this.users = users;
}

}

复制代码

复制代码

@Entity public 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,Integer> {
User findByUserName(String userName);
}

依赖是

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

四、Service

复制代码

public interface UserService {
User findByUserName(String userName);
} public interface LoginService {
LoginResult login(String userName,String password); void logout();
}

复制代码

五、Service.impl

复制代码

@Service public 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;
}
}

复制代码

复制代码

@Service public 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,配置类

shiro核心配置

复制代码

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;
}

}

复制代码

复制代码

@Configuration public class ShiroConfig {
@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>(); // 配置不会被拦截的链接 顺序判断,因为前端模板采用了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

HomeController用来处理登录

复制代码

@Controller public 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<String, Object> map,HttpServletRequest request)
{
    loginService.logout(); return "/user/login";
}

@RequestMapping(value \= "/login",method = RequestMethod.POST) public String login(Map<String, Object> 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";
}

}

复制代码

UserController用来测试访问,权限全部采用注解的方式。

复制代码

@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

Login.html

复制代码

<title\>用户登录</title\>

<style\> .bgColor{ background-color:rgba(243,66,111,0.15) } .divBorder{ border: solid 1px rgba(12,24,255,0.15); padding: 10px; margin-top: 10px; border-radius: 10px; text-align: center; vertical-align: middle;
    } .h4font{ margin-top: 0px; font-family: 微软雅黑; font-weight: 500;
    } .center { padding: 20% 0;
    }

</style\>

</head>
<body>

用户登录

            <br\>
            <input type\="submit" class\="btn btn-lg btn-block btn-info" value\="登 录"\>
        </form\>
    </div\>

</div\>

</div>

</body>
</html>

复制代码

九、数据库里预设一些数据

注意admin的密码是123456,这里保存的是加密后的密码,根据前面的设置,是md5,散列2次。

登录的时候shiro会根据配置自动给密码123456加密,然后与数据库里取出的密码比对。

注意先运行一遍程序,JPA生成数据库表后,手工执行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);

复制代码

十、其他配置

Application.yml

复制代码

server:
servlet:
context-path: / port: 80 spring:
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

复制代码

所有依赖:

复制代码

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <dependency>
        <groupId>nz.net.ultraq.thymeleaf</groupId>
        <artifactId>thymeleaf-layout-dialect</artifactId>
    </dependency>

    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-java8time</artifactId>
        <!--<version>3.0.1.RELEASE</version>-->
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>
</dependencies>

复制代码

目录图:

其他的html页面自己随便生成就可以。

到此基本实现简单的权限管理。

代码可以参考这个地址:https://github.com/asker124143222/bus