springboot学习14——整合shiro

在idea里建一个springboot项目
代码结构:
springboot学习14——整合shiro_第1张图片
懒得建表,所有使用了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,后续再详细讲下认证跟鉴权的步骤。

你可能感兴趣的:(shiro,spring,springboot,shiro)