SpringBoot+Shiro实现登陆拦截功能

      上一章讲到使用自定义的方式来实现用户登录的功能,这章采用shiro来实现用户登陆拦截的功能。

      首先介绍下Shiro:Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理,以下是shiro的整体的框架:

Subject: 即"用户",外部应用都是和Subject进行交互的,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权(Subject相当于SecurityManager的门面)。

SecurityManager: 即安全管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。此外SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口

Authenticator:是一个执行对用户的身份验证(登录)的组件。通过与一个或多个Realm 协调来存储相关的用户/帐户信息。从Realm中找到对应的数据,明确是哪一个登陆人。如果存在多个realm,则接口AuthenticationStrategy(策略)会确定什么样算是登录成功(例如,如果一个Realm成功,而其他的均失败,是否登录成功?)。它是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

Authorizer:即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。就是用来判断是否有权限,授权,本质就是访问控制,控制哪些URL可以访问.

Realm:即领域,相当于datasource数据源securityManager进行安全认证需要通过Realm获取用户权限数据,通常一个数据源配置一个realm.s比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码

SessionDAO:即会话dao,是对session会话操作的一套接口,SessionDao代替sessionManager来代替对session进行增删改查,允许用户使用任何类型的数据源来存储session数据,也可以将数据引入到session框架来。比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

CacheManager:缓存管理,用于管理其他shiro组件中维护和创建的cache实例,维护这些cache实例的生命周期,缓存那些从后台获取的用于用户权限,验证的数据,将它们存储在缓存,这样可以提高性能顺序:先从缓存中查找,再从后台其他接口从其它数据源中进行查找,可以用其他现代的企业级数据源来代替默认的数据源来提高性能

Cryptography:密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

我们可以把和shiro的交互用下图来表示:


这个是Shiro身份认证的流程图:

(注:这个图片是从其他博客拷贝过来的,)

这是Shiro的认证流程:

流程如下

1、首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;

2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例

3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限

4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。

 

ModularRealmAuthorizer进行多Realm匹配流程

1、首先检查相应的Realm是否实现了实现了Authorizer

2、如果实现了Authorizer,那么接着调用其相应的isPermitted*/hasRole*接口进行匹配

3、如果有一个Realm匹配那么将返回true,否则返回false。

 

如果Realm进行授权的话,应该继承AuthorizingRealm,其流程是:

1.1、如果调用hasRole*,则直接获取AuthorizationInfo.getRoles()与传入的角色比较即可

1.2、如果调用如isPermitted(“user:view”),首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;

2、通过AuthorizationInfo.getObjectPermissions()得到Permission实例集合;通过AuthorizationInfo. getStringPermissions()得到字符串集合并通过PermissionResolver解析为Permission实例;然后获取用户的角色并通过RolePermissionResolver解析角色对应的权限集合(默认没有实现,可以自己提供);

3、接着调用Permission. implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则false

现在开始上代码:

Pom.xml


   
   
  1. <!-- 支持JSP,必须导入这两个依赖 -->
  2. <dependency>
  3. <groupId>org.apache.tomcat.embed </groupId>
  4. <artifactId>tomcat-embed-jasper </artifactId>
  5. <scope>provided </scope>
  6. </dependency>
  7. <dependency>
  8. <groupId>javax.servlet.jsp.jstl </groupId>
  9. <artifactId>jstl-api </artifactId>
  10. <version>1.2 </version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework.boot </groupId>
  14. <artifactId>spring-boot-starter </artifactId>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.springframework.boot </groupId>
  18. <artifactId>spring-boot-starter-test </artifactId>
  19. <scope>test </scope>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.boot </groupId>
  23. <artifactId>spring-boot-starter-web </artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.apache.shiro </groupId>
  27. <artifactId>shiro-spring </artifactId>
  28. <version>1.4.0 </version>
  29. </dependency>
  30. <dependency>
  31. <groupId>postgresql </groupId>
  32. <artifactId>postgresql </artifactId>
  33. <version>8.4-702.jdbc4 </version>
  34. </dependency>
  35. <dependency>
  36. <groupId>org.postgresql </groupId>
  37. <artifactId>postgresql </artifactId>
  38. <scope>runtime </scope>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.springframework.boot </groupId>
  42. <artifactId>spring-boot-devtools </artifactId>
  43. <optional>true </optional>
  44. </dependency>
  45. <dependency>
  46. <groupId>org.mybatis.spring.boot </groupId>
  47. <artifactId>mybatis-spring-boot-starter </artifactId>
  48. <version>1.3.0 </version>
  49. </dependency>
  50. <dependency>
  51. <groupId>com.alibaba </groupId>
  52. <artifactId>druid </artifactId>
  53. <version>1.0.20 </version>
  54. </dependency>
  55. <dependency>
  56. <groupId>org.apache.commons </groupId>
  57. <artifactId>commons-lang3 </artifactId>
  58. <version>3.4 </version>
  59. </dependency>

这边还用了Mybatis的内容,需要读者自行去学习相关的知识,这里不详细介绍了。

项目的整体预览:

login.jsp:这边是一个简单的form表单


   
   
  1. <form action="/loginUser" method="post">
  2. <input type="text" name="username"> <br>
  3. <input type="password" name="password"> <br>
  4. <input type="submit" value="提交">
  5. </form>

index.jsp:简单的展示界面

<h1> 欢迎登录, ${user.username} </h1>
   
   

Unauthorized.jsp:自定义跳转的无权限界面


   
   
  1. <body>
  2. Unauthorized!
  3. </body>

appliaction.yml:


   
   
  1. server:
  2. port: 8081
  3. session-timeout: 30
  4. tomcat.max-threads: 0
  5. tomcat.uri-encoding: UTF-8
  6. spring:
  7. datasource:
  8. type: com.alibaba.druid.pool.DruidDataSource
  9. driver-class-name: org.postgresql.Driver
  10. url: jdbc:postgresql:// 服务器地址:5432/库名
  11. username: XXXXX
  12. password: XXXXX
  13. mvc:
  14. view:
  15. prefix: /pages/
  16. suffix: .jsp
  17. mybatis:
  18. mapper-locations: mappers/*.xml
  19. type-aliases-pacakage: com.Pojo #映射的类型在Pojo下面

这是存放的相对位置

TestController:控制器类


   
   
  1. @Controller
  2. public class TestController {
  3. @RequestMapping( "/login")
  4. public String login() {
  5. return "login";
  6. }
  7. @RequestMapping( "/index")
  8. public String index() {
  9. return "index";
  10. }
  11. @RequestMapping( "/logout")
  12. public String logout() {
  13. Subject subject = SecurityUtils.getSubject(); //取出当前验证主体
  14. if (subject != null) {
  15. subject.logout(); //不为空,执行一次logout的操作,将session全部清空
  16. }
  17. return "login";
  18. }
  19. @RequestMapping( "unauthorized")
  20. public String unauthorized() {
  21. return "unauthorized";
  22. }
  23. @RequestMapping( "/admin")
  24. @ResponseBody //注解之后只是返回json数据,不返回界面
  25. public String admin() {
  26. return "admin success";
  27. }
  28. @RequestMapping( "/edit")
  29. @ResponseBody
  30. public String edit() {
  31. return "edit success";
  32. }
  33. /*
  34. * 整个form表单的验证流程:
  35. *
  36. * 将登陆的用户/密码传入UsernamePasswordToken,当调用subject.login(token)开始,调用Relam的doGetAuthenticationInfo方法,开始密码验证
  37. * 此时这个时候执行我们自己编写的CredentialMatcher(密码匹配器),执行doCredentialsMatch方法,具体的密码比较实现在这实现
  38. *
  39. * */
  40. @RequestMapping( "/loginUser")
  41. public String loginUser(@RequestParam("username") String username,
  42. @RequestParam("password") String password,
  43. HttpSession session) {
  44. UsernamePasswordToken token = new UsernamePasswordToken(username, password);
  45. Subject subject = SecurityUtils.getSubject();
  46. try {
  47. System.out.println( "获取到信息,开始验证!!");
  48. subject.login(token); //登陆成功的话,放到session中
  49. User user = (User) subject.getPrincipal();
  50. session.setAttribute( "user", user);
  51. return "index";
  52. } catch (Exception e) {
  53. return "login";
  54. }
  55. }
  56. }

ShiroConfiguration.java:自定义了Shiro的配置器


   
   
  1. package com.Auth;
  2. import org.apache.shiro.cache.MemoryConstrainedCacheManager;
  3. import org.apache.shiro.mgt.SecurityManager;
  4. import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  5. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  6. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  7. import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
  8. import org.springframework.beans.factory.annotation.Qualifier;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Configuration;
  11. import java.util.LinkedHashMap;
  12. @Configuration
  13. public class ShiroConfiguration {
  14. //@Qualifier代表spring里面的
  15. @Bean( "shiroFilter")
  16. public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
  17. ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
  18. bean.setSecurityManager(manager);
  19. bean.setLoginUrl( "/login"); //提供登录到url
  20. bean.setSuccessUrl( "/index"); //提供登陆成功的url
  21. bean.setUnauthorizedUrl( "/unauthorized");
  22. /*
  23. * 可以看DefaultFilter,这是一个枚举类,定义了很多的拦截器authc,anon等分别有对应的拦截器
  24. * */
  25. LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
  26. filterChainDefinitionMap.put( "/index", "authc"); //代表着前面的url路径,用后面指定的拦截器进行拦截
  27. filterChainDefinitionMap.put( "/login", "anon");
  28. filterChainDefinitionMap.put( "/loginUser", "anon");
  29. filterChainDefinitionMap.put( "/admin", "roles[admin]"); //admin的url,要用角色是admin的才可以登录,对应的拦截器是RolesAuthorizationFilter
  30. filterChainDefinitionMap.put( "/edit", "perms[edit]"); //拥有edit权限的用户才有资格去访问
  31. filterChainDefinitionMap.put( "/druid/**", "anon"); //所有的druid请求,不需要拦截,anon对应的拦截器不会进行拦截
  32. filterChainDefinitionMap.put( "/**", "user"); //所有的路径都拦截,被UserFilter拦截,这里会判断用户有没有登陆
  33. bean.setFilterChainDefinitionMap(filterChainDefinitionMap); //设置一个拦截器链
  34. return bean;
  35. }
  36. /*
  37. * 注入一个securityManager
  38. * 原本以前我们是可以通过ini配置文件完成的,代码如下:
  39. * 1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
  40. Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
  41. 2、得到SecurityManager实例 并绑定给SecurityUtils
  42. SecurityManager securityManager = factory.getInstance();
  43. SecurityUtils.setSecurityManager(securityManager);
  44. * */
  45. @Bean( "securityManager")
  46. public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) {
  47. //这个DefaultWebSecurityManager构造函数,会对Subject,realm等进行基本的参数注入
  48. DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
  49. manager.setRealm(authRealm); //往SecurityManager中注入Realm,代替原本的默认配置
  50. return manager;
  51. }
  52. //自定义的Realm
  53. @Bean( "authRealm")
  54. public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher) {
  55. AuthRealm authRealm = new AuthRealm();
  56. //这边可以选择是否将认证的缓存到内存中,现在有了这句代码就将认证信息缓存的内存中了
  57. authRealm.setCacheManager( new MemoryConstrainedCacheManager());
  58. //最简单的情况就是明文直接匹配,然后就是加密匹配,这里的匹配工作则就是交给CredentialsMatcher来完成
  59. authRealm.setCredentialsMatcher(matcher);
  60. return authRealm;
  61. }
  62. /*
  63. * Realm在验证用户身份的时候,要进行密码匹配
  64. * 最简单的情况就是明文直接匹配,然后就是加密匹配,这里的匹配工作则就是交给CredentialsMatcher来完成
  65. * 支持任意数量的方案,包括纯文本比较、散列比较和其他方法。除非该方法重写,否则默认值为
  66. * */
  67. @Bean( "credentialMatcher")
  68. public CredentialMatcher credentialMatcher() {
  69. return new CredentialMatcher();
  70. }
  71. /*
  72. * 以下AuthorizationAttributeSourceAdvisor,DefaultAdvisorAutoProxyCreator两个类是为了支持shiro注解
  73. * */
  74. @Bean
  75. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
  76. AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
  77. advisor.setSecurityManager(securityManager);
  78. return advisor;
  79. }
  80. @Bean
  81. public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
  82. DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
  83. creator.setProxyTargetClass( true);
  84. return creator;
  85. }
  86. }

这里自定义了AuthRealm,CredentialsMatcher,来看看它们具体的代码:


   
   
  1. public class AuthRealm extends AuthorizingRealm{ //AuthenticatingRealm是抽象类,用于认证
  2. @Autowired
  3. private UserService userService;
  4. /*
  5. * 真实授权抽象方法,供子类调用
  6. *
  7. * 这个是当登陆成功之后会被调用,看当前的登陆角色是有有权限来进行操作
  8. * */
  9. @Override
  10. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  11. System.out.println( "doGetAuthorizationInfo方法");
  12. User user = (User) principals.fromRealm( this.getClass().getName()).iterator().next();
  13. List<String> permissionList = new ArrayList<>();
  14. List<String> roleNameList = new ArrayList<>();
  15. Set<Role> roleSet = user.getRoles(); //拿到角色
  16. if (CollectionUtils.isNotEmpty(roleSet)) {
  17. for(Role role : roleSet) {
  18. roleNameList.add(role.getRname()); //拿到角色
  19. Set<Permission> permissionSet = role.getPermissions();
  20. if (CollectionUtils.isNotEmpty(permissionSet)) {
  21. for (Permission permission : permissionSet) {
  22. permissionList.add(permission.getName());
  23. }
  24. }
  25. }
  26. }
  27. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  28. info.addStringPermissions(permissionList); //拿到权限
  29. info.addRoles(roleNameList); //拿到角色
  30. return info;
  31. }
  32. /*
  33. * 用于认证登录,认证接口实现方法,该方法的回调一般是通过subject.login(token)方法来实现的
  34. * AuthenticationToken 用于收集用户提交的身份(如用户名)及凭据(如密码):
  35. * AuthenticationInfo是包含了用户根据username返回的数据信息,用于在匹马比较的时候进行相互比较
  36. *
  37. * shiro的核心是java servlet规范中的filter,通过配置拦截器,使用拦截器链来拦截请求,如果允许访问,则通过。
  38. * 通常情况下,系统的登录、退出会配置拦截器。登录的时候,调用subject.login(token),token是用户验证信息,
  39. * 这个时候会在Realm中doGetAuthenticationInfo方法中进行认证。这个时候会把用户提交的验证信息与数据库中存储的认证信息,将所有的数据拿到,在匹配器中进行比较
  40. * 这边是我们自己实现的CredentialMatcher类的doCredentialsMatch方法,返回true则一致,false则登陆失败
  41. * 退出的时候,调用subject.logout(),会清除回话信息
  42. *
  43. * */
  44. @Override
  45. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  46. System.out.println( "将用户,密码填充完UsernamePasswordToken之后,进行subject.login(token)之后");
  47. UsernamePasswordToken userpasswordToken = (UsernamePasswordToken) token; //这边是界面的登陆数据,将数据封装成token
  48. String username = userpasswordToken.getUsername();
  49. User user = userService.findByUsername(username);
  50. return new SimpleAuthenticationInfo(user,user.getPassword(), this.getClass().getName());
  51. }
  52. }

   
   
  1. /*
  2. * 密码校验方法继承SimpleCredentialsMatcher或HashedCredentialsMatcher类,自定义实现doCredentialsMatch方法
  3. * */
  4. public class CredentialMatcher extends SimpleCredentialsMatcher {
  5. /*
  6. * 这里是进行密码匹配的方法,自己定义
  7. * 通过用户的唯一标识得到 AuthenticationInfo 然后和 AuthenticationToken (用户名 密码),进行比较
  8. * */
  9. @Override
  10. public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
  11. System.out.println( "这边是密码校对");
  12. UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
  13. String password = new String(usernamePasswordToken.getPassword());
  14. String dbPassword = (String) info.getCredentials(); //数据库里的密码
  15. return this.equals(password, dbPassword);
  16. }
  17. }

UserMapper.java:


   
   
  1. public interface UserMapper {
  2. User findByUsername(@Param("username") String username);
  3. }

UserMapper对应的UserMapper.xml如下:


   
   
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.Mapper.UserMapper">
  4. <resultMap id="userMap" type="com.Pojo.User">
  5. <id property="uid" column="uid" />
  6. <id property="username" column="username" />
  7. <id property="password" column="password" />
  8. <collection property = "roles" ofType="com.Pojo.Role">
  9. <id property="rid" column="rid" />
  10. <id property="rname" column="rname" />
  11. <collection property="permissions" ofType="com.Pojo.Permission">
  12. <id property="pid" column="pid" />
  13. <id property="name" column="name" />
  14. <id property="url" column="url" />
  15. </collection>
  16. </collection>
  17. </resultMap>
  18. <select id="findByUsername" parameterType="string" resultMap="userMap">
  19. SELECT u.*, r.*, p.*
  20. FROM "user" u
  21. INNER JOIN user_role ur on ur.uid = u.uid
  22. INNER JOIN role r on r.rid = ur.rid
  23. INNER JOIN permission_role pr on pr.rid = r.rid
  24. INNER JOIN permission p on pr.pid = p.pid
  25. WHERE u.username = #{username}
  26. </select>
  27. </mapper>

这边注意,在我springBoot的启动类中,已经把包扫描了@MapperScan("com.Mapper")

@SpringBootApplication
@MapperScan("com.Mapper")
public class SpringBootShiroApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootShiroApplication.class, args);
    }
}

这边还有service类,和service的实现类:

public interface UserService {
   User findByUsername(String username);
}

@Service
public class UserServiceImpl implements UserService{
    @Resource
    private UserMapper userMapper;
    @Override
    public User findByUsername(String username) {
        return userMapper.findByUsername(username);
    }

}

另外这边也定义了几个Pojo:

User.java:用户类


   
   
  1. public class User {
  2. private Integer uid;
  3. private String username;
  4. private String password;
  5. private Set<Role> roles = new HashSet<Role>();
  6. public Integer getUid() {
  7. return uid;
  8. }
  9. public void setUid(Integer uid) {
  10. this.uid = uid;
  11. }
  12. public String getUsername() {
  13. return username;
  14. }
  15. public void setUsername(String username) {
  16. this.username = username;
  17. }
  18. public String getPassword() {
  19. return password;
  20. }
  21. public void setPassword(String password) {
  22. this.password = password;
  23. }
  24. public Set<Role> getRoles() {
  25. return roles;
  26. }
  27. public void setRoles(Set<Role> roles) {
  28. this.roles = roles;
  29. }
  30. }

Permission.java:权限类


   
   
  1. public class Permission {
  2. private Integer pid;
  3. private String name;
  4. private String url;
  5. public Integer getPid() {
  6. return pid;
  7. }
  8. public void setPid(Integer pid) {
  9. this.pid = pid;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public String getUrl() {
  18. return url;
  19. }
  20. public void setUrl(String url) {
  21. this.url = url;
  22. }
  23. }

Role.java:角色类


   
   
  1. public class Role {
  2. private Integer rid;
  3. private String rname;
  4. private Set<Permission> permissions = new HashSet<>(); //一个角色有多个权限
  5. private Set<User> users = new HashSet<>();
  6. public Integer getRid() {
  7. return rid;
  8. }
  9. public void setRid(Integer rid) {
  10. this.rid = rid;
  11. }
  12. public String getRname() {
  13. return rname;
  14. }
  15. public void setRname(String rname) {
  16. this.rname = rname;
  17. }
  18. public Set<Permission> getPermissions() {
  19. return permissions;
  20. }
  21. public void setPermissions(Set<Permission> permissions) {
  22. this.permissions = permissions;
  23. }
  24. public Set<User> getUsers() {
  25. return users;
  26. }
  27. public void setUsers(Set<User> users) {
  28. this.users = users;
  29. }
  30. }

具体的sql如下:


   
   
  1. -------权限表------
  2. CREATE TABLE permission
  3. (
  4. pid serial NOT NULL,
  5. name character varying( 255) NOT NULL,
  6. url character varying( 255),
  7. CONSTRAINT permission_pkey PRIMARY KEY (pid)
  8. )
  9. WITH (
  10. OIDS= FALSE
  11. );
  12. ALTER TABLE permission
  13. OWNER TO logistics;
  14. INSERT INTO permission values( '1', 'add', '')
  15. INSERT INTO permission values( '2', 'delete', '')
  16. INSERT INTO permission values( '3', 'edit', '')
  17. INSERT INTO permission values( '4', 'query', '')
  18. -------用户表------
  19. CREATE TABLE "user"
  20. (
  21. uid serial NOT NULL,
  22. username character varying( 255) NOT NULL,
  23. password character varying( 255),
  24. CONSTRAINT user_pkey PRIMARY KEY (uid)
  25. )
  26. WITH (
  27. OIDS= FALSE
  28. );
  29. ALTER TABLE "user"
  30. OWNER TO logistics;
  31. INSERT INTO "user" values( '1', 'admin', '123456')
  32. INSERT INTO "user" values( '2', 'demo', '123456')
  33. -------角色表------
  34. CREATE TABLE role
  35. (
  36. rid serial NOT NULL,
  37. rname character varying( 255) NOT NULL,
  38. CONSTRAINT role_pkey PRIMARY KEY (rid)
  39. )
  40. WITH (
  41. OIDS= FALSE
  42. );
  43. ALTER TABLE role
  44. OWNER TO logistics;
  45. INSERT INTO role values( '1', 'admin')
  46. INSERT INTO role values( '2', 'customer')
  47. -----权限角色关系表-----
  48. CREATE TABLE permission_role
  49. (
  50. rid integer NOT NULL,
  51. pid integer NOT NULL,
  52. CONSTRAINT permission_role_pkey PRIMARY KEY (pid,rid)
  53. )
  54. WITH (
  55. OIDS= FALSE
  56. );
  57. ALTER TABLE permission_role
  58. OWNER TO logistics;
  59. INSERT INTO permission_role values( 1, 1)
  60. INSERT INTO permission_role values( 1, 2)
  61. INSERT INTO permission_role values( 1, 3)
  62. INSERT INTO permission_role values( 1, 4)
  63. INSERT INTO permission_role values( 2, 1)
  64. INSERT INTO permission_role values( 2, 4)
  65. -----用户角色关系表-----
  66. CREATE TABLE user_role
  67. (
  68. rid integer NOT NULL,
  69. uid integer NOT NULL,
  70. CONSTRAINT user_role_pkey PRIMARY KEY (uid, rid)
  71. )
  72. WITH (
  73. OIDS= FALSE
  74. );
  75. ALTER TABLE user_role
  76. OWNER TO logistics;
  77. INSERT INTO user_role values( 1, 1)
  78. INSERT INTO user_role values( 2, 2)

此时我们开始测试:

输入localhost:8081/admin,由于我们在ShiroConfiguration中配置了一个拦截器链,对应的URL路径都会被对应的拦截器给拦截来处理。


   
   
  1. LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
  2. filterChainDefinitionMap.put( "/index", "authc"); //代表着前面的url路径,用后面指定的拦截器进行拦截
  3. filterChainDefinitionMap.put( "/login", "anon");
  4. filterChainDefinitionMap.put( "/loginUser", "anon");
  5. filterChainDefinitionMap.put( "/admin", "roles[admin]"); //admin的url,要用角色是admin的才可以登录,对应的拦截器是RolesAuthorizationFilter
  6. filterChainDefinitionMap.put( "/edit", "perms[edit]"); //拥有edit权限的用户才有资格去访问
  7. filterChainDefinitionMap.put( "/druid/**", "anon"); //所有的druid请求,不需要拦截,anon对应的拦截器不会进行拦截
  8. filterChainDefinitionMap.put( "/**", "user"); //所有的路径都拦截,被UserFilter拦截,这里会判断用户有没有登陆
  9. bean.setFilterChainDefinitionMap(filterChainDefinitionMap); //设置一个拦截器链

这里我们可以看到,admin路径是被roles对应的拦截器RolesAuthorizationFilter拦截,在方法isAccessAllowed中进行处理,判断是不是admin角色的用户,是这个角色的才可以访问,否则前往自己定义的无权限界面,这里别名对应的拦截器是在DefaultFilter这个枚举类中有定义:


   
   
  1. anon(AnonymousFilter.class),
  2. authc(FormAuthenticationFilter.class),
  3. authcBasic(BasicHttpAuthenticationFilter.class),
  4. logout(LogoutFilter.class),
  5. noSessionCreation(NoSessionCreationFilter.class),
  6. perms(PermissionsAuthorizationFilter.class),
  7. port(PortFilter.class),
  8. rest(HttpMethodPermissionFilter.class),
  9. roles(RolesAuthorizationFilter.class),
  10. ssl(SslFilter.class),
  11. user(UserFilter.class);

由于现在没有登录,所以一开始会前往登录界面,填写用户账号和密码,点击提交,因为我们form表单的action是loginUser,此时数据提交到Controller中对应的处理方法中:


   
   
  1. /*
  2. * 整个form表单的验证流程:
  3. *
  4. * 将登陆的用户/密码传入UsernamePasswordToken,当调用subject.login(token)开始,调用Relam的doGetAuthenticationInfo方法,开始密码验证
  5. * 此时这个时候执行我们自己编写的CredentialMatcher(密码匹配器),执行doCredentialsMatch方法,具体的密码比较实现在这实现
  6. *
  7. * */
  8. @RequestMapping( "/loginUser")
  9. public String loginUser(@RequestParam("username") String username,
  10. @RequestParam("password") String password,
  11. HttpSession session) {
  12. UsernamePasswordToken token = new UsernamePasswordToken(username, password);
  13. Subject subject = SecurityUtils.getSubject();
  14. try {
  15. System.out.println( "获取到信息,开始验证!!");
  16. subject.login(token); //登陆成功的话,放到session中
  17. User user = (User) subject.getPrincipal();
  18. session.setAttribute( "user", user);
  19. return "index";
  20. } catch (Exception e) {
  21. return "login";
  22. }
  23. }

我们会把用户名,密码存入到UsernamePasswordToken中,UsernamePasswordToken是一个用户,密码认证令牌,里面有用户名,密码,是否缓存等属性。然后代码就会跳转到我们自己编写的Realm--AuthRealm的doGetAuthenticationInfo方法(具体可以看这篇博文https://www.cnblogs.com/ccfdod/p/6436353.html 这理由详细的介绍,这个代码调用如下:subject.login(token)-->DelegatingSubject类的login方法-->SecurityManager的login-->DefaultSecurityManager的login方法-->AuthenticatingSecurityManager的authenticate方法-->实现类AuthenticatingRealm中的getAuthenticationInfo方法)。在我们自己的getAuthenticationInfo方法中,我们根据用户名查询出用户的信息,返回AuthenticationInfo对象,如果token与获取到的AuthenticationInfo都不为空,缓存AuthenticationInfo信息。接着代码会跳转到我们的凭证验证的方法CredentialMatcher类的doCredentialsMatch方法:


   
   
  1. @Override
  2. public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
  3. System.out.println( "这边是密码校对");
  4. UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
  5. String password = new String(usernamePasswordToken.getPassword());
  6. String dbPassword = (String) info.getCredentials(); //数据库里的密码
  7. return this.equals(password, dbPassword);
  8. }

其实我们在调用AuthenticatingRealm的getAuthenticationInfo方法时:


   
   
  1. public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  2. AuthenticationInfo info = getCachedAuthenticationInfo(token);
  3. if (info == null) {
  4. //otherwise not cached, perform the lookup:
  5. info = doGetAuthenticationInfo(token);
  6. log.debug( "Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
  7. if (token != null && info != null) {
  8. cacheAuthenticationInfoIfPossible(token, info);
  9. }
  10. } else {
  11. log.debug( "Using cached authentication info [{}] to perform credentials matching.", info);
  12. }
  13. if (info != null) {
  14. assertCredentialsMatch(token, info);
  15. } else {
  16. log.debug( "No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
  17. }
  18. return info;
  19. }

当AuthenticationInfo查出来不为空时,进行凭证密码匹配,调用assertCredentialsMatch(token,info):


   
   
  1. protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
  2. CredentialsMatcher cm = getCredentialsMatcher();
  3. if (cm != null) {
  4. if (!cm.doCredentialsMatch(token, info)) {
  5. //not successful - throw an exception to indicate this:
  6. String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
  7. throw new IncorrectCredentialsException(msg);
  8. }
  9. } else {
  10. throw new AuthenticationException( "A CredentialsMatcher must be configured in order to verify " +
  11. "credentials during authentication. If you do not wish for credentials to be examined, you " +
  12. "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
  13. }
  14. }

这边调用cm.doCredentialsMatch(token, info)方法,这边要阐述下CredentialsMatcher是一个接口,用来凭证密码匹配的,继承并实现doCredentialsMatch方法即可,这边我们自定义的CredentialMatcher类,继承了SimpleCredentialsMatcher类,而SimpleCredentialsMatcher实现了CredentialsMatcher方法。所以继续接着上面思路的进入我们的密码匹配方法,如果匹配正确则返回true,如果验证失败则返回false。此时一个完整的登录验证完成。

那么当我们继续访问其他的URL时,会进入我们授权的方法,AuthRealm类的doGetAuthorizationInfo(),主要是拿到登录用户的角色和权限,以此判断该用户是否有权限进入URL,没有权限则被跳转到unauthorized.jsp界面,( bean.setUnauthorizedUrl("/unauthorized");--原先设定的没有访问权限的情况)。

   这篇文章就讲到这,如果读者有补充,或者页面中有不对的地方请指正。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值