在springboot中使用shiro,由于没有了xml配置文件,因此使用的方法与spring中有些区别。在踩了无数个坑后,在此将springboot下使用shiro的步骤总结如下。
由于本人对shiro的了解不是很深入,在实现了工作需求后就没有继续研究了,因此可能存在遗漏的地方或有错误的地方,还请多包涵。
目标
- 在springboot中使用shiro
- 1.实现用户的登录验证
- 2.对于一些指定的url使用自定义的filter验证方式(不再使用shiro的realm验证)
步骤
1.在pom.xml中添加shiro的依赖
1
2
3
4
5
|
< dependency >
< groupId >org.apache.shiro</ groupId >
< artifactId >shiro-spring</ artifactId >
< version >1.3.2</ version >
</ dependency >
|
2.创建ShiroRealm.java
继承AuthorizingRealm类,重写登录认证方法与授权方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import User;
import UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
public class ShiroRealm extends AuthorizingRealm {
//自己编写的service,注入,执行数据库查询方法用
@Autowired
private UserService userService;
//认证.登录
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken utoken=(UsernamePasswordToken) token; //获取用户输入的token
String username = utoken.getUsername();
//这个User对象为自定义的JavaBean,使用userService从数据库中得到User对象(包含用户名、密码、权限3个字段)
User user = userService.findUserByUserName(username);
return new SimpleAuthenticationInfo(user, user.getPassword(), this .getClass().getName()); //放入shiro.调用CredentialsMatcher检验密码
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
User user=(User) principal.fromRealm( this .getClass().getName()).iterator().next(); //获取session中的用户
List<String> permissions= new ArrayList<>();
//使用自定义的user对象获得权限字段,string类型,装入集合
permissions.add(user.getRole());
SimpleAuthorizationInfo info= new SimpleAuthorizationInfo();
info.addStringPermissions(permissions); //将权限放入shiro中.(我的代码中其实没有用到权限相关)
return info;
}
}
|
3.创建ShiroConfiguration.java
使用@Configuration注解,是shiro的配置类,类似xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
@Configuration
public class ShiroConfiguration {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
/*重要,设置自定义拦截器,当访问某些自定义url时,使用这个filter进行验证*/
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
//如果map里面key值为authc,表示所有名为authc的过滤条件使用这个自定义的filter
//map里面key值为myFilter,表示所有名为myFilter的过滤条件使用这个自定义的filter,具体见下方
filters.put( "myFilter" , new MyFilter());
shiroFilterFactoryBean.setFilters(filters);
/*---------------------------------------------------*/
//拦截器
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
//配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put( "/logout" , "logout" );
// anon:所有url都都可以匿名访问;
// authc: 需要认证才能进行访问;
// user:配置记住我或认证通过可以访问;
//放开静态资源的过滤
filterChainDefinitionMap.put( "/css/**" , "anon" );
filterChainDefinitionMap.put( "/js/**" , "anon" );
filterChainDefinitionMap.put( "/img/**" , "anon" );
//放开登录url的过滤
filterChainDefinitionMap.put( "/loginController" , "anon" );
///
//对于指定的url,使用自定义filter进行验证
filterChainDefinitionMap.put( "/targetUrl" , "myFilter" );
//可以配置多个filter,用逗号分隔,按顺序过滤,下方表示先通过自定义filter的验证,再通过shiro默认过滤器的验证
//filterChainDefinitionMap.put("/targetUrl", "myFilter,authc");
///
//过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
//url从上向下匹配,当条件匹配成功时,就会进入指定filter并return(不会判断后续的条件),因此这句需要在最下边
filterChainDefinitionMap.put( "/**" , "authc" );
//如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl( "/login" );
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl( "/loginSuccess" );
// 未授权界面
shiroFilterFactoryBean.setUnauthorizedUrl( "/403" );
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
return securityManager;
}
//配置核心安全事务管理器
@Bean (name= "securityManager" )
public SecurityManager securityManager( @Qualifier ( "shiroRealm" ) ShiroRealm shiroRealm) {
DefaultWebSecurityManager manager= new DefaultWebSecurityManager();
manager.setRealm(shiroRealm);
return manager;
}
//配置自定义的权限登录器
@Bean (name= "shiroRealm" )
public ShiroRealm shiroRealm( @Qualifier ( "credentialsMatcher" ) CredentialsMatcher matcher) {
ShiroRealm shiroRealm= new ShiroRealm();
shiroRealm.setCredentialsMatcher(matcher);
return shiroRealm;
}
//配置自定义的密码比较器
@Bean (name= "credentialsMatcher" )
public CredentialsMatcher credentialsMatcher() {
return new CredentialsMatcher();
}
}
|
4.创建自定义的过滤器MyFilter.java
继承AccessControlFilter类,在步骤3中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;
public class MyFilter extends AccessControlFilter {
private static Logger log = LoggerFactory.getLogger(MyFilter. class );
//判断是否拦截,false为拦截,true为允许
@Override
public boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object object) throws Exception {
Subject subject = getSubject(req,resp);
String url = getPathWithinApplication(req);
log.info( "当前用户正在访问的url为 " + url);
log.info( "subject.isPermitted(url);" +subject.isPermitted(url));
//可自行根据需要判断是否拦截,可以获得subject判断用户权限,也可以使用req获得请求头请求体信息
//return true;
return false ;
}
//上面的方法返回false后(被拦截),会进入这个方法;这个方法返回false表示处理完毕(不放行);返回true表示需要继续处理(放行)
@Override
public boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//从req中获得的值,也可以自己使用其它判断是否放行的方法
String username = request.getParameter( "name" );
String password = request.getParameter( "password" );
//创建token对象
UsernamePasswordToken usernamePasswordToken= new UsernamePasswordToken(username,password);
Subject subject = SecurityUtils.getSubject();
try {
//使用subject对象的login方法验证该token能否登录(使用方法2中的shiroRealm.java中的方法验证)
subject.login(usernamePasswordToken);
} catch (Exception e) {
//log.info("登陆失败");
//log.info(e.getMessage());
return false ;
}
//log.info("登陆成功");
return true ;
}
}
|
5.步骤3中使用了自定义密码验证的方式
因此需要创建类CredentialsMatcher.java(与步骤3中的名称对应),继承SimpleCredentialsMatcher类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
public class CredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken utoken=(UsernamePasswordToken) token;
//获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
String inPassword = new String(utoken.getPassword());
//获得数据库中的密码
String dbPassword=(String) info.getCredentials();
//进行密码的比对
return this .equals(inPassword, dbPassword);
}
}
|
6.步骤3中放开了对登录页/loginController的过滤
因此我增加了一个ShiroController.java类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
@Controller
public class ShiroController {
@RequestMapping ( "/" )
public String loginPage() {
return "login" ;
}
@RequestMapping ( "/login" )
public String login() {
return "login" ;
}
@RequestMapping ( "/loginController" )
public String loginUser(String username,String password,HttpSession session) {
UsernamePasswordToken usernamePasswordToken= new UsernamePasswordToken(username,password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(usernamePasswordToken); //完成登录
//自定义的JavaBean,用于保存用户名、密码、权限3个字段
User user=(User) subject.getPrincipal();
//可选,可放入session,以备后续使用
session.setAttribute( "user" , user);
//跳转到登录成功页(controller)
return "forward:/loginSuccess" ;
} catch (Exception e) {
//登录失败,跳转回登录页(html)
return "login" ;
}
}
@RequestMapping ( "/logOut" )
public String logOut(HttpSession session) {
Subject subject = SecurityUtils.getSubject();
subject.logout();
//session.removeAttribute("user");
return "login" ;
}
}
|
个人经验
1.shiro配置类中的url拦截的执行顺序为从上到下,如果url匹配到一个规则,则会跳出匹配方法,忽略后续的匹配规则(相当于return)。
2.shiro使用自定义filter时,最好继承shiro的filter,不要直接继承Filter类。
3.shiro使用自定义filter时,map集合的key配置为"authc"、value配置为"new MyFilter()"时,表示对配置为authc的url使用自定义filter进行拦截,而不会使用ShiroRealm中的验证方法验证(可能是将shiro默认的authc的拦截器覆盖了);因此最好将key配置为其它自定义的字符串,将部分url的拦截规则设置为使用自定义filter拦截即可(如果仍想使用shiro默认的拦截器,可用逗号连接"authc")。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/BHSZZY/article/details/85328353