前言
SpringBoot整合shiro,需要写一个配置类和一个Realm即可,这里只讲实现功能,不讲原理。
1.引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2.实体类
user
import javax.persistence.*;
@Entity
@Table
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
@Column
private String password;
@OneToOne
@JoinColumn(name = "roleId")
private Role role;
@Column
private String salt;
public User() {
}
public User(String name, String password, Role role, String salt) {
this.name = name;
this.password = password;
this.role = role;
this.salt = salt;
}
public Integer getId() {
return id;
}
public void setId(Integer 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;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", role=" + role +
", salt='" + salt + '\'' +
'}';
}
}
Role
@Entity
@Table
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer roleId;
@Column
private String roleName;
public Role() {
}
public Role(String roleName) {
this.roleName = roleName;
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
@Override
public String toString() {
return "Role{" +
"roleId=" + roleId +
", roleName='" + roleName + '\'' +
'}';
}
}
3.配置文件
application.yml
#SpringBoot热部署
spring:
devtools:
restart:
enabled: true
additional-paths: src/main/java
exclude: WEB-INF/**
freemarker:
cache: false
datasource:
username: root
password: admin
url: jdbc:mysql://localhost:3306/shiro?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
4.生成表的结构
因为使用了JPA,表是自动生成的,下面是表的结构:
user
role
那么到这一步,准备工作已经完成了,在表设计中,我们设置一个用户有一个角色(OneToOne)
5.Realm类
package cn.yf.springboot_shiro_02.realm;
import cn.yf.springboot_shiro_02.bean.User;
import cn.yf.springboot_shiro_02.repository.UserRepository;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
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 java.util.HashSet;
import java.util.Set;
public class MyRealm extends AuthorizingRealm {
@Autowired
UserRepository userRepository;
//权限认证
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) SecurityUtils.getSubject().getPrincipal();
User user = userRepository.findByName(username).get(0);
String role = user.getRole().getRoleName();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> stringSet = new HashSet<>();
stringSet.add(role);
info.setRoles(stringSet);
return info;
}
//登陆认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
User user = userRepository.findByName(username).get(0);
if(user != null){
//交给shiro判断
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,user.getPassword(),getName());
//设置盐
simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
return simpleAuthenticationInfo;
}else{
return null;
}
}
}
这个类继承于AuthorizingRealm 并且重写了它的两个方法,doGetAuthorizationInfo用于权限设置和doGetAuthenticationInfo用于登陆设置
6.配置类
package cn.yf.springboot_shiro_02.config;
import cn.yf.springboot_shiro_02.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class MyShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//没有登录的用户请求登录时自动跳转到登录页面
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/register", "anon");
filterChainDefinitionMap.put("/loginUser","anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/test/**", "authc");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(MyRealm myRealm){
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(myRealm);
return defaultSecurityManager;
}
@Bean
public MyRealm myRealm(){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
myRealm.setCachingEnabled(false);
return myRealm;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
// storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
7.测试代码
package cn.yf.springboot_shiro_02.controller;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("test")
@RestController
public class TestController {
@RequiresRoles("admin")
@RequestMapping("add")
public String add(){
return "添加";
}
@RequiresRoles("admin")
@RequestMapping("delete")
public String delete(){
return "删除";
}
@RequiresRoles("admin")
@RequestMapping("update")
public String update(){
return "更新";
}
@RequiresRoles(value={"user","admin"},logical= Logical.OR)
@RequestMapping("query")
public String query(){
return "查询";
}
}
8.演示
注册
账号:abc
密码:123456
在数据库中就可以看到加密后的密码,后面是它的盐
登陆
下面是关于权限的测试
根据上面的测试代码可以知道,当角色为admin时可以访问增删改查,而为user时只能访问查。
插入数据,我们可以知道,yf可以访问增删改查,而tht只能访问查
下面开始测试
登陆:yf
登陆tht
这样一个简单的权限认证和登陆验证,我们就完成了。我这里没有贴前端和一些跳转配置类的代码
9.总结
登陆注册加密的过程是这样的:首先当我们注册时,输入账号密码,首先生成盐,然后根据Shiro提供的Md5Hash类进行md5加密密码,然后把盐和密码都存入数据库中,当我们登陆时,从数据库取出盐和密码,把登陆时的密码和数据库中的盐进行md5加密,然后将结果和数据库中的密码进行匹配,如果结果一样就通过,不一样就抛出异常