在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,後續再詳細講下認證跟鑑權的步驟。