在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
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.0.RELEASE
com.example
shiro
0.0.1-SNAPSHOT
war
shiro
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.apache.shiro
shiro-spring
1.4.0
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-data-mongodb
com.google.guava
guava
18.0
org.springframework.boot
spring-boot-maven-plugin
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 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 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 userRoles = mongoTemplate.find(queryUserRole, UserRole.class);
Set 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 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 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 filterChainDefinitionMap = new HashMap();
//开放登陆接口
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,后续再详细讲下认证跟鉴权的步骤。