这是基于b站狂神的SprinBoot课程的笔记 课程链接
0. 环境搭建
首先搭建一个springboot整合mybats的环境,代码略
实体类为:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
// 用户权限
private String perm;
}
为了做登录验证,在UserMapper中加入一个按用户名查询用户的方法:
// 按用户名查询用户
User getUserByName(String name);
再编写与之对应的mapper.xml和UserService类,这里代码略。
做一个基础的前端主页
首页
首页
登录
add
update
和与之相对应的Controller
@Controller
public class MyController {
// 主页跳转
@RequestMapping({"/", "/index"})
public String toIndex(Model model) {
model.addAttribute("msg", "Hello, SpringBoot-shiro!");
return "index";
}
// add界面跳转
@RequestMapping("/user/add")
public String toAdd() {
return "user/add";
}
// update界面跳转
@RequestMapping("/user/update")
public String toUpdate() {
return "user/update";
}
// 登录页面跳转
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
需求是:
- 登录认证
- 登录的用户根据自身的权限,可以访问add或update界面
1. 登录认证
shiro环境搭建(固定代码)
首先编写一个realm类,管理用户的授权和认证
@Component
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("=========执行了授权doGetAuthorizationInfo=========");
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("=========执行了认证doGetAuthenticationInfo=========");
return null;
}
}
接着编写shiro配置类
@Configuration
public class ShiroConfig {
// 1. 配置UserRealm
public UserRealm getUserRealm() {
return new UserRealm();
}
// 2. 配置SecurityManager
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联自定义的UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
// 3. 配置ShiroFilterFactoryBean
@Bean(name = "shiroFilterFactoryBean") // autoconfigure.ShiroWebFilterConfiguration required a bean named 'shiroFilterFactoryBean'
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 关联securityManager
bean.setSecurityManager(securityManager);
return bean;
}
}
开始实现功能:
首先,要对未登录的用户对user目录下的add,update页面访问的请求进行拦截,在配置ShiroFilterFactoryBean的方法中加入拦截规则
// 3. 配置ShiroFilterFactoryBean
@Bean(name = "shiroFilterFactoryBean") // autoconfigure.ShiroWebFilterConfiguration required a bean named 'shiroFilterFactoryBean'
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 关联securityManager
bean.setSecurityManager(securityManager);
// 拦截功能
// 添加shiro的内置过滤器
/*
anon:无需认证
authc:需要认证
user:需要 记住我
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
Map filterMap = new LinkedHashMap<>();
// 登录认证拦截:user目录下的所有内容需要认证才能访问
filterMap.put("/user/*", "authc");
bean.setFilterChainDefinitionMap(filterMap);
// 若没有登录认证,设置登录请求
bean.setLoginUrl("/toLogin");
return bean;
}
这样任何没有登录的用户点击add或update的超链接,都会拦截并被转到登录页面
接着我们要处理用户登录
先写一个登录界面
Login
登录
接着我们要对输入的用户名和密码进行验证 --> 在UserRealm类中的 doGetAuthenticationInfo
方法中处理用户认证(Authentication)
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("=========执行了认证doGetAuthenticationInfo=========");
// 这个token中封装了从前端传来的用户名和密码
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 用户名,密码 -- 数据库取
// 调用Mapper层的按用户名找用户的方法
// 使用从前端传来的用户名在数据库中找用户
User user = userService.getUserByName(token.getUsername());
// 用户名认证
if (user==null) { //用户不存在
return null; // 抛出 UnknownAccountException 异常
}
// 将密码认证交给shiro做
// 将正在登录的用户传进subject中
return new SimpleAuthenticationInfo(user, user.getPwd(), "");
}
编写点击submit后会转到的controller
这里注意,在 doGetAuthenticationInfo
中若有用户名或密码认证不成功的,在执行subject.login(token)
会抛出相应的异常,这就实现了用户登录认证
@RequestMapping("/login")
public String login(String username, String password, Model model) {
// 获取登录用户
Subject subject = SecurityUtils.getSubject();
// 封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 认证逻辑
try {
// 登录,若没有异常,则登录成功
subject.login(token);
// 若登录成功,则在session中放入登陆成功的用户
subject.getSession().setAttribute("loginUser", subject.getPrincipal());
return "index";
} catch (UnknownAccountException e) { // 用户名不存在
// 给前端传递用户名错误信息
model.addAttribute("msg", "用户名不存在");
// 返回登录页面
return "login";
} catch (IncorrectCredentialsException e) { // 密码错误
// 给前端传递密码错误信息
model.addAttribute("msg", "密码错误");
// 返回登录页面
return "login";
}
2.用户权限处理
首先在shiro配置类中配置 ShiroFilterFactoryBean
中的拦截功能中,加入权限拦截功能
// 3. 配置ShiroFilterFactoryBean
@Bean(name = "shiroFilterFactoryBean") // autoconfigure.ShiroWebFilterConfiguration required a bean named 'shiroFilterFactoryBean'
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 关联securityManager
bean.setSecurityManager(securityManager);
// 拦截功能
// 添加shiro的内置过滤器
/*
anon:无需认证
authc:需要认证
user:需要 记住我
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
Map filterMap = new LinkedHashMap<>();
// 权限拦截:
// add页面只有拥有add权限的用户才能进入
// 待解决:权限拦截只有写在登录认证拦截前面才有效,为什么?
filterMap.put("/user/add", "perms[add]");
// update页面只有拥有update权限的用户才能进入
filterMap.put("/user/update", "perms[update]");
// 登录认证拦截:user目录下的所有内容需要认证才能访问
filterMap.put("/user/*", "authc");
bean.setFilterChainDefinitionMap(filterMap);
// 若没有登录认证,设置登录请求
bean.setLoginUrl("/toLogin");
// 若没有足够权限,设置未授权页面
bean.setUnauthorizedUrl("/unauth");
return bean;
}
所以现在访问user/add需要有add权限,访问user/update需要有update权限
接着,我们就要在UserRealm中的 doGetAuthorizationInfo
中给有这些权限的用户授权 (Authorization)
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("=========执行了授权doGetAuthorizationInfo=========");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 拿到当前用户
Subject subject = SecurityUtils.getSubject();
User curUser = (User) subject.getPrincipal();
// 按当前登录的user的权限来给其授权
info.addStringPermission(curUser.getPerm());
return info;
}
这里注意,我们要在 doGetAuthenticationInfo
方法中给subject放入principal,也就是我们的user
// 将正在登录的用户传进subject中
return new SimpleAuthenticationInfo(user, user.getPwd(), "");
然后我们才能通过 subject.getPrincipal()
取到当前用户
这样我们就实现了权限拦截与用户授权
我们最后shiro整合thymeleaf,将内容只显示给有权限的用户
先要在config类中加入shiro整和thymeleaf的组件
// ShiroDialect:用来整合 shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
比如,只有有add权限的用户才会有访问add界面的超链接
最后展示一下完整代码
ShiroConfig.java -- 连接shiro的Subject, SecurityManager, 和Realm三大核心组件,并在ShiroFilterFactoryBean中配置登录和权限拦截的规则:
@Configuration
public class ShiroConfig {
// 1. 配置UserRealm
@Bean
public UserRealm getUserRealm() {
return new UserRealm();
}
// 2. 配置SecurityManager
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联自定义的UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
// 3. 配置ShiroFilterFactoryBean
@Bean(name = "shiroFilterFactoryBean") // autoconfigure.ShiroWebFilterConfiguration required a bean named 'shiroFilterFactoryBean'
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 关联securityManager
bean.setSecurityManager(securityManager);
// 拦截功能
// 添加shiro的内置过滤器
/*
anon:无需认证
authc:需要认证
user:需要 记住我
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
Map filterMap = new LinkedHashMap<>();
// 权限拦截:
// add页面只有拥有add权限的用户才能进入
// 待解决:权限拦截只有写在登录认证拦截前面才有效,为什么?
filterMap.put("/user/add", "perms[add]");
// update页面只有拥有update权限的用户才能进入
filterMap.put("/user/update", "perms[update]");
// 登录认证拦截:user目录下的所有内容需要认证才能访问
filterMap.put("/user/*", "authc");
bean.setFilterChainDefinitionMap(filterMap);
// 若没有登录认证,设置登录请求
bean.setLoginUrl("/toLogin");
// 若没有足够权限,设置未授权页面
bean.setUnauthorizedUrl("/unauth");
return bean;
}
// ShiroDialect:用来整合 shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
}
UserRealm.java -- 认证要登录的用户,给登录的用户授权
@Component
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("=========执行了授权doGetAuthorizationInfo=========");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 拿到当前用户
Subject subject = SecurityUtils.getSubject();
User curUser = (User) subject.getPrincipal();
// 按当前登录的user的权限来给其授权
info.addStringPermission(curUser.getPerm());
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("=========执行了认证doGetAuthenticationInfo=========");
// 用户名,密码 -- 数据库取
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 试图得到登录的用户
User user = userService.getUserByName(token.getUsername());
// 用户名认证
if (user==null) { //用户不存在
return null; // 抛出 UnknownAccountException 异常
}
// 将密码认证交给shiro做
// 将正在登录的用户传进subject中
return new SimpleAuthenticationInfo(user, user.getPwd(), "");
}
}
MyController.java -- 控制页面跳转
@Controller
public class MyController {
@RequestMapping({"/", "/index"})
public String toIndex(Model model) {
model.addAttribute("msg", "Hello, SpringBoot-shiro!");
return "index";
}
@RequestMapping("/user/add")
public String toAdd() {
return "user/add";
}
@RequestMapping("/user/update")
public String toUpdate() {
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
@RequestMapping("/login")
public String login(String username, String password, Model model) {
// 获取登录用户
Subject subject = SecurityUtils.getSubject();
// 封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 认证逻辑
try {
// 登录,若没有异常,则登录成功
subject.login(token);
// 若登录成功,则在session中放入登陆成功的用户
subject.getSession().setAttribute("loginUser", subject.getPrincipal());
return "index";
} catch (UnknownAccountException e) {
model.addAttribute("msg", "用户名不存在");
return "login";
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg", "密码错误");
return "login";
}
}
@RequestMapping("/unauth")
@ResponseBody
public String unauthorized() {
return "您没有访问该页面的权限!";
}
}
index.html -- 主页
首页
首页
login.html 登录页
Login
登录