前言
网上大多数关于springboot整合shiro的教程是非前后端分离的,认证、授权失败的处理都是页面跳转,不是返回json数据,本文旨在解决这一问题。
pom.xml 只copy了重要的部分出来,spring-boot-starter-web、lombok(辅助工具,个人习惯)、spring-boot-starter-aop(不要忘了)、shiro-spring
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-aop
org.apache.shiro
shiro-spring
1.5.3
实际使用中应使用Mybatis或JPA与数据库数据对应,本案例简化使用内存写死两个用户做说明
注解都是lombok,为了不用写Get Set 方法,与本文要解决的问题非强相关
Accout.java 实际案例中用户的permission role应为多个,即使用Map存储多个,此处简化处理
@Getter
@Setter
@AllArgsConstructor
public class Accout {
private String name;
private String password;
private String permis;
private String roles;
}
创建接口,通过用户名返回用户
创建service类实现接口,此处写死了两个用户,user1 user2,分别带有不同权限
public interface AccoutService {
public Accout checkuser(String username);
}
/文件分割/
@Service
public class AccoutServiceImp implements AccoutService{
@Override
public Accout checkuser(String username) {
Accout user1 = new Accout(“user1”,“123456”,“permis1”,“role1”);
Accout user2 = new Accout(“user2”,“123456”,“permis2”,“role2”);
if(username.equals("user1")){return user1;}
if(username.equals("user2")){return user2;}
return null;
}
}
创建接口,向前端返回JSON数据
创建service类实现接口
public interface ResponseFactory {
void makeResponse(HttpServletResponse res, String code, String msg) throws IOException;
}
/文件分割/
@Service
public class ResponseFactoryImp implements ResponseFactory {
@Override
public void makeResponse(HttpServletResponse res, String code, String msg) throws IOException {
res.setContentType(“application/json; charset=utf-8”);
Map
result.put(“code”, code);
result.put(“msg”, msg);
res.getWriter().write(result.toString());
}
}
调用写好的用户服务,通过用户名获取用户
doGetAuthorizationInfo 用于认证通过的用户授予权限
doGetAuthenticationInfo 用于对用户进行认证,是否合法用户
public class AccoutRealm extends AuthorizingRealm {
@Autowired
private AccoutServiceImp accoutServiceImp;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//拿到当前用户,认证成功时候放进来的。
Subject subject = SecurityUtils.getSubject();
Accout accout = (Accout) subject.getPrincipal();
if(accout != null){
//把用户里有的权限都给他赋予上
Set roles = new HashSet<>();
roles.add(accout.getRoles());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
info.addStringPermission(accout.getPermis());
return info;
}
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从token中获取用户名,取出用户
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
Accout accout = accoutServiceImp.checkuser(token.getUsername());
if(accout != null)
{
//没有传token进去,不知道如何进行密码比对的,可以打断点自己分析下
return new SimpleAuthenticationInfo(accout,accout.getPassword(),getName());
}
return null;
}
}
主要是把自己写的 Realm注入
@Configuration
public class ShiroConfig{
//把注入了自己的Realm的defaultWebSecurityManager注入ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
return shiroFilterFactoryBean;
}
//把自己写的Realm注入defaultWebSecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(accoutRealm());
return manager;
}
//把自己写的Realm放入spring容器中
@Bean
public AccoutRealm accoutRealm(){
return new AccoutRealm();
}
//不加这一段,不执行doGetAuthorizationInfo 授权,不知道为什么
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
login需要采用post方式发送username password到后端,注意subject.login会调用doGetAuthenticationInfo认证,产生的异常我们统一另作处理即可
manage只由拥有 permis1 用户权限才可调用
@RestController
public class AccoutController {
@RequestMapping(value="/login",method = RequestMethod.POST)
public String login(@RequestParam("username") String username,@RequestParam("password") String password){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
return "{code:200,msg:login success!";
}
@GetMapping("/manage")
@RequiresPermissions("permis1")
public String manage(){
return "manage page!";
}
@GetMapping("/administrator")
public String administrator(){
return "administrator page!";
}
@GetMapping("/main")
public String mainpage(){
return "main page!";
}
}
@ControllerAdvice
public class AuthException {
@Autowired
private ResponseFactoryImp myresponse;
//异常未完全列举
@ExceptionHandler(value = UnauthorizedException.class)//处理访问方法时权限不足问题
public void AuthcErrorHandler(HttpServletResponse res, Exception e) throws IOException {
myresponse.makeResponse(res,"1","权限不足!"+e.toString());
}
@ExceptionHandler(value = UnknownAccountException.class) //处理未知账号
public void UnKnowAccountErrorHandler(HttpServletResponse res, Exception e) throws IOException {
myresponse.makeResponse(res,"2","未知账号!"+e.toString());
}
@ExceptionHandler(value = IncorrectCredentialsException.class) //处理账号凭证异常
public void IncorrectCredentialErrorHandler(HttpServletResponse res, Exception e) throws IOException {
myresponse.makeResponse(res,"3","凭证异常!"+e.toString());
}
@ExceptionHandler(value = AuthorizationException.class) //处理账号未登录
public void AuthorizationErrorHandler(HttpServletResponse res, Exception e) throws IOException {
myresponse.makeResponse(res,"4","请登录后访问!"+e.toString());
}
}
─shiro
│ ShiroApplication.java //启动类
│
├─config
│ AuthException.java
│ ShiroConfig.java
│
├─controller
│ AccoutController.java
│
├─Entity
│ Accout.java
│
├─MyResPonse
│ ResponseFactory.java
│ ResponseFactoryImp.java
│
├─realm
│ AccoutRealm.java
│
└─service
AccoutService.java
AccoutServiceImp.java
未登录直接访问不需要权限的方法

未登录直接访问需要权限的方法

访问登录方法,输入不存在的用户

访问登录方法,输入存在的用户,密码错误

访问登录方法,输入存在的用户,且密码正确

登录后访问当前用户没有权限的方法

登录后访问不需要权限的方法

登录后访问当前用户有权限的方法

解决了springboot整合shiro 在前后端分离场景的应用,但对于 登录时限、单点登录等功能限制还需继续完善。
1.1 AuthenticationException 异常是Shiro在登录认证过程中,认证失败需要抛出的异常。 AuthenticationException包含以下子类:
1.2AuthorizationException 权限校验子类: