springboot學習14——整合shiro

在idea裏建一個springboot項目
代碼結構:
在這裏插入圖片描述
懶得建表,所有使用了mongodb
1.application.properties

server.port=8090

#在Spring Boot中配置mongodb:
spring.data.mongodb.host=127.0.0.1
#spring.data.mongodb.username=spring
#spring.data.mongodb.password=123456
spring.data.mongodb.port=27017
spring.data.mongodb.database=springboot

2.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>shiro</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>shiro</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!--shiro-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>
		<!--lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<!--mongodb-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
		</dependency>
		<!--guava-->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>18.0</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

3.5個java pojo,分別是用戶,角色,權限,用戶角色關係,角色權限關係

@Data
@Builder
@AllAr
gsConstructor
@NoArgsConstructor
@Document(collection = "tms_user")
public class User {
    /**
     * 帳號id
     */
    @Id
    private String id;

    /**
     * 名稱
     */
    private String username;

    /**
     * 密碼
     */
    private String password;

    /**
     * 鹽
     */
    private String salt;

    /**
     * 狀態 0:正常 1:鎖定
     */
    private Integer locked;

    /**
     * 是否admin;0:否,1:是
     */
    private Integer root;

    /**
     * 有效;0:無效,1:有效
     */
    private Integer active;

    /**
     * 用戶對應的角色集合
     */
    private Set<Role> roles;

    public String getCredentialsSalt() {
        return username + salt + salt;
    }

}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "tms_role")
public class Role {
    /**
     * 角色id
     */
    @Id
    private String id;

    /**
     * 角色名稱
     */
    @Field("role_name")
    private String roleName;

    /**
     * 0:禁用;1:啓用
     */
    private Integer valid;

    /**
     * 有效;0:無效,1:有效
     */
    private Integer active;

    /**
     * 角色對應權限集合
     */
    private Set<Permission> permissions;

}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "tms_permission")
public class Permission {
    /**
     * id
     */
    @Id
    private String id;

    /**
     * 名稱
     */
    @Field("permissions_name")
    private String permissionsName;

    /**
     * 有效;0:無效,1:有效
     */
    private Integer active;

    /**
     * 所屬上級
     */
    private Integer pid;

    /**
     * 類型 1:菜單 2:按鈕
     */
    private Integer type;

    /**
     * 訪問路徑
     */
    private String url;

    /**
     * 菜單級別(1:一級;2:二級)
     */
    private Integer level;

    /**
     * 按鈕事件
     */
    private String event;

}

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(collection = "tms_user_role")
public class UserRole{
    /**
     * id
     */
    @Id
    private String id;

    /**
     * 角色ID
     */
    @Field("role_id")
    private String roleId;

    /**
     * 角色ID
     */
    @Field("user_id")
    private String userId;

    /**
     * 有效;0:無效,1:有效
     */
    private Integer active;

}

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(collection = "tms_role_permission")
public class RolePermission {

    /**
     * ID
     */
    @Id
    private String id;

    /**
     * 角色ID
     */
    @Field("role_id")
    private String roleId;

    /**
     * 權限ID
     */
    @Field("permission_id")
    private String permissionId;


    /**
     * 有效;0:無效,1:有效
     */
    private Integer active;

}

4.service及其實現,主要是查用戶的相關信息,新增用戶

public interface UserService {

    User getUserByName(String name);

    void saveUser(User user, String roleName);

}
@Service
public class UserServiceImpl implements UserService {

    // 注入MongoTemplate對象
    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public User getUserByName(String name) {
        //查找用戶
        Query query = new Query();
        query.addCriteria(Criteria.where("username").is(name));
        User user = mongoTemplate.find(query, User.class).get(0);
        //查找用戶角色
        Query queryUserRole = new Query();
        queryUserRole.addCriteria(Criteria.where("user_id").is(user.getId()));
        List<UserRole> userRoles = mongoTemplate.find(queryUserRole, UserRole.class);
        Set<Role> roles=Sets.newHashSet();
        for (UserRole userRole: userRoles) {
            Query queryRole = new Query();
            queryRole.addCriteria(Criteria.where("id").is(userRole.getRoleId()));
            Role role = mongoTemplate.find(queryRole, Role.class).get(0);
            //查找角色權限
            Query queryRolePermission = new Query();
            queryRolePermission.addCriteria(Criteria.where("role_id").is(role.getId()));
            List<RolePermission> rolePermissions = mongoTemplate.find(queryRolePermission, RolePermission.class);
            Query queryPermission = new Query();
            queryPermission.addCriteria(Criteria.where("id").in(rolePermissions.stream().map(o->o.getPermissionId()).collect(Collectors.toSet())));
            List<Permission> permissions = mongoTemplate.find(queryPermission, Permission.class);
            role.setPermissions(Sets.newHashSet(permissions));
            roles.add(role);
        }
        user.setRoles(roles);
        return user;
    }

    @Override
    public void saveUser(User user,String roleName) {
        user.setLocked(0);
        user.setRoot(1);
        user.setActive(1);
        mongoTemplate.insert(user);
        System.out.println("userId:"+user.getId());
        Query queryRole = new Query();
        queryRole.addCriteria(Criteria.where("role_name").is(roleName));
        Role role = mongoTemplate.find(queryRole, Role.class).get(0);
        UserRole userRole = UserRole.builder().roleId(role.getId()).userId(user.getId()).active(1).build();
        mongoTemplate.insert(userRole);
    }
}

5.controller,有2個,一個是用來學習認證,一個用來學習鑑權

/**
 * 不受權限控制訪問的地址
 */
@RestController
@RequestMapping("login")
public class LoginController {

    @Autowired
    private UserService userService;
    @Autowired
    private PasswordHelper passwordHelper;

    @GetMapping("login")
    public Object login() {
        return "Here is Login page";
    }

    @GetMapping("unauthc")
    public Object unauthc() {
        return "Here is Unauthc page";
    }

    @GetMapping("doLogin")
    public Object doLogin(@RequestParam String username, @RequestParam String password) {
        //得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證)
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //通過 SecurityUtils 得到 Subject,其會自動綁定到當前線程
        Subject subject = SecurityUtils.getSubject();
        try {
            //調用 subject.login 方法進行登錄,其會自動委託給 SecurityManager.login 方法進行登錄
            // 會進入CustomRealm中的方法,具體進入哪個,要看過濾器的配置
            subject.login(token);
        } catch (IncorrectCredentialsException ice) {
            //對於頁面的錯誤消息展示,最好使用如 “用戶名 / 密碼錯誤” 而不是 “用戶名錯誤”/“密碼錯誤”,防止一些惡意用戶非法掃描帳號庫,此處只是測試
            return "password error!";
        } catch (UnknownAccountException uae) {
            return "username error!";
        }
        User user = userService.getUserByName(username);
        subject.getSession().setAttribute("user", user);
        return "SUCCESS";
    }

    @GetMapping("register")
    public Object register(@RequestParam String username, @RequestParam String password,@RequestParam String roleName) {
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        passwordHelper.encryptPassword(user);
        userService.saveUser(user,roleName);
        return "SUCCESS";
    }
}

/**
 * 需要指定權限可以訪問的地址
 */
@RestController
@RequestMapping("authc")
public class AuthcController {

    @GetMapping("index")
    public Object index() {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getSession().getAttribute("user");
        return user.toString();
    }

    @GetMapping("role1")
    public Object role1() {
        return "Welcome role1";
    }

    // permission
    @GetMapping("permission")
    public Object removable() {
        return "permission";
    }

    // permission1
    @GetMapping("permission1")
    public Object renewable() {
        return "permission1";
    }
}

6.自定義的Realm:爲用戶做認證和授權,很重要

/**
 * 自定義的Realm:爲用戶做認證和授權
 */
public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 獲取授權信息
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("————權限認證————");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        String username = (String) principals.getPrimaryPrincipal();
        User user = userService.getUserByName(username);
        //將用戶所擁有的角色和權限賦值到authorizationInfo中ShiroConfig中的過濾器配置
        for (Role role : user.getRoles()) {
            authorizationInfo.addRole(role.getRoleName());
            for (Permission permission : role.getPermissions()) {
                authorizationInfo.addStringPermission(permission.getPermissionsName());
            }
        }
        return authorizationInfo;
    }

    /**
     * 獲取身份驗證信息:封裝好SimpleAuthenticationInfo後,交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配
     * @param token 用戶身份信息 token
     * @return 返回封裝了用戶信息的 AuthenticationInfo 實例
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("————身份認證方法————");
        String username = (String) token.getPrincipal();
        User user = userService.getUserByName(username);
        if (user == null) {
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
                ByteSource.Util.bytes(user.getCredentialsSalt()), getName());
        return authenticationInfo;
    }

}

7.輔助類:密碼加密

/**
 * 在創建賬戶及修改密碼時直接把生成密碼操作委託給 PasswordHelper
 */
public class PasswordHelper {
    private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
    public static final String ALGORITHM_NAME = "md5"; // 基礎散列算法
    public static final int HASH_ITERATIONS = 2; // 自定義散列次數

    public void encryptPassword(User user) {
        // 隨機字符串作爲salt因子,實際參與運算的salt我們還引入其它干擾因子
        user.setSalt(randomNumberGenerator.nextBytes().toHex());
        String newPassword = new SimpleHash(ALGORITHM_NAME, user.getPassword(),
                ByteSource.Util.bytes(user.getCredentialsSalt()), HASH_ITERATIONS).toHex();
        user.setPassword(newPassword);
    }
}

8.配置類:很重要

@Configuration
public class ShiroConfig {

    /**
     * Shiro通過一系列filter來控制訪問權限,並在它的內部爲我們預先定義了多個過濾器,我們可以直接通過字符串配置這些過濾器。
     常用的過濾器如下:
     authc:所有已登陸用戶可訪問
     roles:有指定角色的用戶可訪問,通過[ ]指定具體角色,這裏的角色名稱與數據庫中配置一致
     perms:有指定權限的用戶可訪問,通過[ ]指定具體權限,這裏的權限名稱與數據庫中配置一致
     anon:所有用戶可訪問,通常作爲指定頁面的靜態資源時使用
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        //Shiro的Web過濾器
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必須設置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //setLoginUrl 如果不設置值,默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 或 "/login" 映射,沒有身份認證時,跳轉頁面
        shiroFilterFactoryBean.setLoginUrl("/login/login");
        //已經進行了身份認證,但沒有權限時跳轉的 url;
        shiroFilterFactoryBean.setUnauthorizedUrl("/login/unauthc");

        // 設置攔截器
        Map<String, String> filterChainDefinitionMap = new HashMap<String, String>();
        //開放登陸接口
        filterChainDefinitionMap.put("/login/*", "anon");
        filterChainDefinitionMap.put("/authc/index", "authc");
        //訪問/authc/role1,用戶需要有role1的角色
        filterChainDefinitionMap.put("/authc/role1", "roles[role1]");
        //訪問/authc/permission,用戶需要有permission1,permission2的權限(兩個的權限都要有)
        filterChainDefinitionMap.put("/authc/permission", "perms[permission1,permission2]");
        //訪問/authc/permission1,用戶需要有permission1的權限
        filterChainDefinitionMap.put("/authc/permission1", "perms[permission1]");

        //其餘接口一律攔截
        //主要這行代碼必須放在所有權限設置的最後,不然會導致所有 url 都被攔截
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        System.out.println("Shiro攔截器工廠類注入成功");
        return shiroFilterFactoryBean;
    }

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName(PasswordHelper.ALGORITHM_NAME); // 散列算法
        hashedCredentialsMatcher.setHashIterations(PasswordHelper.HASH_ITERATIONS); // 散列次數
        return hashedCredentialsMatcher;
    }

    /**
     * 自定義身份認證 realm;
     * 必須寫這個類,並加上 @Bean 註解,目的是注入 CustomRealm,
     * 否則會影響 CustomRealm類 中其他類的依賴注入
     */
    @Bean
    public CustomRealm shiroRealm() {
        CustomRealm shiroRealm = new CustomRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); // 原來在這裏
        return shiroRealm;
    }

    /**
     * DefaultWebSecurityManager在初始化時,注入realm
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
        return securityManager;
    }

    @Bean
    public PasswordHelper passwordHelper() {
        return new PasswordHelper();
    }
}

9.啓動類:加了點數據的初始化

@SpringBootApplication(scanBasePackages = { "com.example.shiro"})
public class ShiroApplication {


	// 注入MongoTemplate對象
	@Autowired
	private MongoTemplate mongoTemplate;

	// 啓用Spring Bean生命週期執行方法,加入插件
	@PostConstruct
	public void initMongo() {
		Role role1 = Role.builder().id("1").roleName("role1").valid(1).active(1).build();
		Role role2 = Role.builder().id("2").roleName("role2").valid(1).active(1).build();
		mongoTemplate.insert(role1);
		mongoTemplate.insert(role2);
		Permission permission1 = Permission.builder().id("1").permissionsName("permission1").pid(0).type(1).url("/").level(1).active(1).build();
		Permission permission2 = Permission.builder().id("2").permissionsName("permission2").pid(0).type(1).url("/").level(1).active(1).build();
		mongoTemplate.insert(permission1);
		mongoTemplate.insert(permission2);
		RolePermission rolePermission1 = RolePermission.builder().id("1").roleId(role1.getId()).permissionId(permission1.getId()).active(1).build();
		RolePermission rolePermission2 = RolePermission.builder().id("2").roleId(role2.getId()).permissionId(permission2.getId()).active(1).build();
		mongoTemplate.insert(rolePermission1);
		mongoTemplate.insert(rolePermission2);
		System.out.println("-------------initMongo----------------");
	}

	public static void main(String[] args) {
		SpringApplication.run(ShiroApplication.class, args);

	}

}

10.測試:

//遊客登錄
http://localhost:8090/login/login

//用戶註冊
http://localhost:8090/login/register?username=kk&password=99&roleName=role1

//登錄,會有身份驗證,然後把用戶信息入到session中
http://localhost:8090/login/doLogin?username=kk&password=99

//登錄後可訪問
http://localhost:8090/authc/index

//登錄後還需要驗證權限
http://localhost:8090/authc/role1
http://localhost:8090/authc/permission
http://localhost:8090/authc/rpermission1

先寫一個小demo,後續再詳細講下認證跟鑑權的步驟。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章