//配置安全管理器
SecurityUtils.setSecurityManager(instance);
//获得Subject对象
Subject subject = SecurityUtils.getSubject();
//创建账号密码token
UsernamePasswordToken user = new UsernamePasswordToken(“wang”, “123”);
//登录验证
subject.login(user);
//权限判断
System.out.println(“是否登录成功:” + subject.isAuthenticated());
System.out.println(“是否拥有role1角色:” + subject.hasRole(“role1”));
System.out.println(“是否拥有delete权限:” + subject.isPermitted(“user:delete”));
使用wang登录的效果:
使用zhang登录的效果:
密码错误会抛出异常:
======================================================================
上面的案例中使用ini文件配置用户、密码、角色和权限等,过于简单,不适合企业级项目的使用,真正项目中用户、角色、权限这些重要数据都保存在数据库中,需要我们通过自己编写的类和方法实现登录和授权。
AuthorizingRealm类实现Realm接口,提供两个方法:
-
doGetAuthenticationInfo 返回登录验证信息,该方法在subject执行login方法后调用
-
doGetAuthorizationInfo 返回用户授权信息,该方法在subject进行权限判断时调用
1) 定义Realm类,此案例为简单起见,账号密码是固定的zhang,123,添加角色role1,添加权限user:select、user:insert、user:delete
/**
- 用户Realm
*/
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获得登录用户
String username = (String) principalCollection.getPrimaryPrincipal();
System.out.println(“登录用户授权:” + username);
//授权信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加角色
info.addRole(“role1”);
//添加权限
info.addStringPermission(“user:select”);
info.addStringPermission(“user:insert”);
info.addStringPermission(“user:delete”);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获得用户输入的账号
String username = authenticationToken.getPrincipal().toString();
if(!username.equals(“zhang”)){
throw new UnknownAccountException(“此用户不存在”);
}
//返回验证信息,参数:1、用户名 2、正确密码 3、realm名称
return new SimpleAuthenticationInfo(username,“123”, getName());
}
}
2) 使用自定义Realm进行登录和授权
//创建默认安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//安全管理器配置自定义Realm
securityManager.setRealm(new MyRealm());
//SecurityUtils配置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//获得Subject对象
Subject subject = SecurityUtils.getSubject();
//创建账号密码token
UsernamePasswordToken user = new UsernamePasswordToken(“zhang”, “123”);
//登录验证
subject.login(user);
//权限判断
System.out.println(“是否拥有role1角色:” + subject.hasRole(“role1”));
System.out.println(“是否拥有delete权限:” + subject.isPermitted(“user:delete”));
====================================================================
用户密码一般不会以明文方式保存,这样无法保证安全性,所以一般都需要加密。
SimpleHash类可以实现基本的加密,几种创建方式:
new SimpleHash(“加密算法”,“原始密码”)
new SimpleHash(“加密算法”,“原始密码”,盐)
new SimpleHash(“加密算法”,“原始密码”,盐,迭代次数)
参数说明:
加密算法一般使用常用的md5算法
盐的作用是提高密码安全性,如两个用户的原始密码都是123,则加密后的密文都是相同的,如果破解了一个用户的密码,另一个用户的密码也一同破解了,如果给密码加盐,每个用户的盐不同,加密后密码就都会不同,增加了破解难度。
迭代次数是加密一次后,再对密文再次加密,也能提高安全性。
下面我们以md5算法对“123456”加密,盐是“007”,迭代次数为10。
SimpleHash md5 = new SimpleHash(“md5”, “123456”,ByteSource.Util.bytes(“007”), 10);
System.out.println(md5);
输出:44202d045439dc33a2e43d2828d08e19
修改MyRealm的doGetAuthenticationInfo方法,这里将密文和盐直接写在代码中,实际应用时密文和盐是通过用户名从数据库中查询出来的。
//返回验证信息,参数:1、用户名 2、正确密码 3、盐 4、realm名称
return new SimpleAuthenticationInfo(username,“44202d045439dc33a2e43d2828d08e19”, ByteSource.Util.bytes(“007”),getName());
需要给自定义Realm添加密码匹配器
//创建默认安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//安全管理器配置自定义Realm
MyRealm realm = new MyRealm();
//创建密码匹配器
HashedCredentialsMatcher md5 = new HashedCredentialsMatcher(“md5”);
//设置迭代次数
md5.setHashIterations(10);
//配置匹配器
realm.setCredentialsMatcher(md5);
securityManager.setRealm(realm);
//SecurityUtils配置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//获得Subject对象
Subject subject = SecurityUtils.getSubject();
//创建账号密码token
UsernamePasswordToken user = new UsernamePasswordToken(“zhang”, “123456”);
//登录验证
subject.login(user);
//权限判断
System.out.println(“是否登录成功:” + subject.isAuthenticated());
System.out.println(“是否拥有role1角色:” + subject.hasRole(“role1”));
System.out.println(“是否拥有delete权限:” + subject.isPermitted(“user:delete”));
========================================================================================
1、表设计
-
s_user 用户表
-
s_role 角色表
-
s_menu 菜单表(权限表)
-
s_user_menu 用户角色中间表
-
s_role_menu 角色菜单中间表
2、添加依赖
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
com.baomidou
mybatis-plus-boot-starter
3.3.2
mysql
mysql-connector-java
8.0.16
org.apache.shiro
shiro-spring-boot-web-starter
1.7.0
org.springframework.boot
spring-boot-starter-thymeleaf
com.github.theborakompanioni
thymeleaf-extras-shiro
2.0.0
2、SpringBoot配置
jdbc配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/erp_db?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
mybatis配置
mybatis-plus.type-aliases-package=com.blb.blb_erp.entity
mybatis-plus.mapper-locations=classpath:mapper/*.xml
shiro配置
登录页面
shiro.loginUrl=/pages/login.html
登录失败跳转页面
shiro.unauthorizedUrl=/pages/failed.html
登录成功跳转页面
shiro.successUrl=/pages/index.html
3、编写Mapper接口
需要三个方法:
-
按用户名查找用户
-
按用户id查询所有菜单
-
按用户id查询所有角色
/**
- 用户接口
*/
public interface SUserMapper extends BaseMapper{
/**
-
通过用户名查询用户
-
@param username
-
@return
*/
SUser selectUserByUsername(String username);
}
映射文件:
<?xml version="1.0" encoding="UTF-8"?>select * from s_user where user_name = #{username}
/**
- 菜单接口
*/
public interface SMenuMapper extends BaseMapper{
/**
-
根据userId查询所有权限
-
@param userId
-
@return
*/
List selectMenusByUserId(String userId);
}
映射文件:
<?xml version="1.0" encoding="UTF-8"?>select m.* from s_user u,s_role r,s_user_role ur,s_menu m,s_role_menu rm
where ur.role_id = r.id and ur.user_id = u.id and rm.role_id = r.id and rm.menu_id = m.id
and u.id = #{userId}
/**
- 角色接口
*/
public interface SRoleMapper extends BaseMapper{
/**
-
根据用户id查询所有角色
-
@param userId
-
@return
*/
List selectRolesByUserId(String userId);
}
映射文件:
<?xml version="1.0" encoding="UTF-8"?>select r.* from s_user_role ur
join s_user u on ur.user_id = u.id
join s_role r on ur.role_id = r.id
where ur.user_id = #{userId}
4、自定义Realm
/**
- 用户Realm
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private SUserMapper userMapper;
@Autowired
private SRoleMapper roleMapper;
@Autowired
private SMenuMapper menuMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获得用户对象
SUser user = (SUser) principalCollection.getPrimaryPrincipal();
//查询权限和角色
List menus = menuMapper.selectMenusByUserId(user.getId());
List roles = roleMapper.selectRolesByUserId(user.getId());
//保存权限和角色名称的集合
List strRoles = new ArrayList<>();
roles.forEach(r -> strRoles.add(r.getRoleName()));
List strMenus = new ArrayList<>();
menus.forEach(m -> strMenus.add(m.getMenuName()));
//返回带有角色和权限名称的授权信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(strRoles);
info.addStringPermissions(strMenus);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获得账号
String username = authenticationToken.getPrincipal().toString();
//通过账号查询用户
SUser user = userMapper.selectUserByUsername(username);
if(user == null){
throw new UnknownAccountException(“此用户不存在”);
}
//返回验证信息 参数:1、用户对象 2、正确密码 3、盐 4、realm名称
return new SimpleAuthenticationInfo(user,user.getPassWord(), ByteSource.Util.bytes(user.getSalt()),getName());
}
}
5、Shiro配置类
/**
- Shiro配置
*/
@Configuration
public class ShiroConfig {
//返回Realm
@Bean
public UserRealm myRealm(){
UserRealm myRealm = new UserRealm();
//设置密码匹配器
HashedCredentialsMatcher md5 = new HashedCredentialsMatcher(“md5”);
md5.setHashIterations(10);
myRealm.setCredentialsMatcher(md5);
//关闭缓存
myRealm.setCachingEnabled(false);
return myRealm;
}
//返回面向Web开发的安全管理器
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
//设置自定义Realm
sm.setRealm(myRealm());
return sm;
}
//返回Shiro过滤器链定义
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
//定义过滤器链key为url,value为anon不验证,authc验证
//anon在前,authc在后,需要使用LinkedHashMap保留顺序
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put(“/pages/login.html”,“anon”);
map.put(“/user/login”,“anon”);
map.put(“/**”,“authc”);
chainDefinition.addPathDefinitions(map);
return chainDefinition;
}
//启动thymeleaf的shiro标签
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
6、启动类
@MapperScan(“com.blb.blb_erp.mapper”)
@SpringBootApplication
public class BlbErpApplication {
public static void main(String[] args) {
SpringApplication.run(BlbErpApplication.class, args);
}
}
7、控制器
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonResult {
private int code;
private Object data;
}
@RestController
@RequestMapping(“/user”)
public class UserController {
@PostMapping(“/login”)
public JsonResult login(String username,String password){
//创建Token
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//获得subject
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return new JsonResult(1,“登录成功”);
}catch (AuthenticationException ex){
ex.printStackTrace();
}
return new JsonResult(0,“账号或密码错误”);
}
@RequiresRoles(“管理员”)
@GetMapping(“/role-admin”)
public String testRole(){
return “有管理员角色”;
}
@RequiresPermissions(“部门管理”)
@GetMapping(“/menu-dept”)
public String testMenu(){
return “有部门管理权限”;
}
}
@RequiresRoles、@RequiresPermissions写在控制器的方法上,登录用户有对应的角色和权限才能访问。
========================================================================
可以在登录页面上添加记住我功能,勾选后下次不用登录直接进去系统了
1、页面上添加RememberMe复选框