标签:架构 账号 secure %s Nid 接口 nat 编写 核心
照例又去官网扒了扒介绍:
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.Apache Shiro™是一个强大且易用的Java安全框架,能够用于身份验证、授权、加密和会话管理。Shiro拥有易于理解的API,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序到最大的网络和企业应用程序。
简而言之,Apache Shiro 是一个强大灵活的开源安全框架,可以完全处理身份验证、授权、加密和会话管理。
Shiro能到底能做些什么呢?
使用 Shiro 官方给了许多令人信服的原因,因为 Shiro 具有以下几个特点:
有兴趣的可以去仔细看看官方的文档:【传送门】
Apache Shiro是一个全面的、蕴含丰富功能的安全框架。下图为描述Shiro功能的框架图:
Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。那么就让我们来看看它们吧:
还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。特别是对以下的功能支持:
注意: Shiro不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给Shiro
在概念层,Shiro 架构包含三个主要的理念:Subject,SecurityManager和 Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。
我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
上图展示了 Shiro 认证的一个重要的过程,为了加深我们的印象,我们来自己动手来写一个例子,来验证一下,首先我们新建一个Maven工程,然后在pom.xml中引入相关依赖:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version></dependency><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version></dependency>
新建一个【AuthenticationTest】测试类:
import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.SimpleAccountRealm;import org.apache.shiro.subject.Subject;import org.junit.Before;import org.junit.Test;public class AuthenticationTest { SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm(); @Before // 在方法开始前添加一个用户 public void addUser() { simpleAccountRealm.addAccount("wmyskxz", "123456"); } @Test public void testAuthentication() { // 1.构建SecurityManager环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(simpleAccountRealm); // 2.主体提交认证请求 SecurityUtils.setSecurityManager(defaultSecurityManager); // 设置SecurityManager环境 Subject subject = SecurityUtils.getSubject(); // 获取当前主体 UsernamePasswordToken token = new UsernamePasswordToken("wmyskxz", "123456"); subject.login(token); // 登录 // subject.isAuthenticated()方法返回一个boolean值,用于判断用户是否认证成功 System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出true subject.logout(); // 登出 System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出false }}
运行之后可以看到预想中的效果,先输出isAuthenticated:true表示登录认证成功,然后再输出isAuthenticated:false表示认证失败退出登录,再来一张图加深一下印象:
流程如下:
跟认证过程大致相似,下面我们仍然通过代码来熟悉一下过程(引入包类似这里节约篇幅就不贴出来了):
public class AuthenticationTest { SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm(); @Before // 在方法开始前添加一个用户,让它具备admin和user两个角色 public void addUser() { simpleAccountRealm.addAccount("wmyskxz", "123456", "admin", "user"); } @Test public void testAuthentication() { // 1.构建SecurityManager环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(simpleAccountRealm); // 2.主体提交认证请求 SecurityUtils.setSecurityManager(defaultSecurityManager); // 设置SecurityManager环境 Subject subject = SecurityUtils.getSubject(); // 获取当前主体 UsernamePasswordToken token = new UsernamePasswordToken("wmyskxz", "123456"); subject.login(token); // 登录 // subject.isAuthenticated()方法返回一个boolean值,用于判断用户是否认证成功 System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出true // 判断subject是否具有admin和user两个角色权限,如没有则会报错 subject.checkRoles("admin","user");// subject.checkRole("xxx"); // 报错 }}
运行测试,能够正确看到效果。
从上面我们了解到实际进行权限信息验证的是我们的 Realm,Shiro 框架内部默认提供了两种实现,一种是查询.ini文件的IniRealm,另一种是查询数据库的JdbcRealm,这两种来说都相对简单,感兴趣的可以去【这里】瞄两眼,我们着重就来介绍介绍自定义实现的 Realm 吧。
有了上面的对认证和授权的理解,我们先在合适的包下创建一个【MyRealm】类,继承 Shirot 框架的 AuthorizingRealm 类,并实现默认的两个方法:
package com.wmyskxz.demo.realm;import org.apache.shiro.authc.*;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import java.util.*;public class MyRealm extends AuthorizingRealm { /** * 模拟数据库数据 */ Map<String, String> userMap = new HashMap<>(16); { userMap.put("wmyskxz", "123456"); super.setName("myRealm"); // 设置自定义Realm的名称,取什么无所谓.. } /** * 授权 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String userName = (String) principalCollection.getPrimaryPrincipal(); // 从数据库获取角色和权限数据 Set<String> roles = getRolesByUserName(userName); Set<String> permissions = getPermissionsByUserName(userName); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setStringPermissions(permissions); simpleAuthorizationInfo.setRoles(roles); return simpleAuthorizationInfo; } /** * 模拟从数据库中获取权限数据 * * @param userName * @return */ private Set<String> getPermissionsByUserName(String userName) { Set<String> permissions = new HashSet<>(); permissions.add("user:delete"); permissions.add("user:add"); return permissions; } /** * 模拟从数据库中获取角色数据 * * @param userName * @return */ private Set<String> getRolesByUserName(String userName) { Set<String> roles = new HashSet<>(); roles.add("admin"); roles.add("user"); return roles; } /** * 认证 * * @param authenticationToken 主体传过来的认证信息 * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 1.从主体传过来的认证信息中,获得用户名 String userName = (String) authenticationToken.getPrincipal(); // 2.通过用户名到数据库中获取凭证 String password = getPasswordByUserName(userName); if (password == null) { return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("wmyskxz", password, "myRealm"); return authenticationInfo; } /** * 模拟从数据库取凭证的过程 * * @param userName * @return */ private String getPasswordByUserName(String userName) { return userMap.get(userName); }}
然后我们编写测试类,来验证是否正确:
import com.wmyskxz.demo.realm.MyRealm;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.subject.Subject;import org.junit.Test;public class AuthenticationTest { @Test public void testAuthentication() { MyRealm myRealm = new MyRealm(); // 实现自己的 Realm 实例 // 1.构建SecurityManager环境 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(myRealm); // 2.主体提交认证请求 SecurityUtils.setSecurityManager(defaultSecurityManager); // 设置SecurityManager环境 Subject subject = SecurityUtils.getSubject(); // 获取当前主体 UsernamePasswordToken token = new UsernamePasswordToken("wmyskxz", "123456"); subject.login(token); // 登录 // subject.isAuthenticated()方法返回一个boolean值,用于判断用户是否认证成功 System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出true // 判断subject是否具有admin和user两个角色权限,如没有则会报错 subject.checkRoles("admin", "user");// subject.checkRole("xxx"); // 报错 // 判断subject是否具有user:add权限 subject.checkPermission("user:add"); }}
运行测试,完美。
在之前的学习中,我们在数据库中保存的密码都是明文的,一旦数据库数据泄露,那就会造成不可估算的损失,所以我们通常都会使用非对称加密,简单理解也就是不可逆的加密,而 md5 加密算法就是符合这样的一种算法。
如上面的 123456 用 Md5 加密后,得到的字符串:e10adc3949ba59abbe56e057f20f883e,就无法通过计算还原回 123456,我们把这个加密的字符串保存在数据库中,等下次用户登录时我们把密码通过同样的算法加密后再从数据库中取出这个字符串进行比较,就能够知道密码是否正确了,这样既保留了密码验证的功能又大大增加了安全性,但是问题是:虽然无法直接通过计算反推回密码,但是我们仍然可以通过计算一些简单的密码加密后的 Md5 值进行比较,推算出原来的密码
比如我的密码是 123456,你的密码也是,通过 md5 加密之后的字符串一致,所以你也就能知道我的密码了,如果我们把常用的一些密码都做 md5 加密得到一本字典,那么就可以得到相当一部分的人密码,这也就相当于“破解”了一样,所以其实也没有我们想象中的那么“安全”。
既然相同的密码 md5 一样,那么我们就让我们的原始密码再加一个随机数,然后再进行 md5 加密,这个随机数就是我们说的盐(salt),这样处理下来就能得到不同的 Md5 值,当然我们需要把这个随机数盐也保存进数据库中,以便我们进行验证。
另外我们可以通过多次加密的方法,即使黑客通过一定的技术手段拿到了我们的密码 md5 值,但它并不知道我们到底加密了多少次,所以这也使得破解工作变得艰难。
在 Shiro 框架中,对于这样的操作提供了简单的代码实现:
String password = "123456";String salt = new SecureRandomNumberGenerator().nextBytes().toString();int times = 2; // 加密次数:2String alogrithmName = "md5"; // 加密算法String encodePassword = new SimpleHash(alogrithmName, password, salt, times).toString();System.out.printf("原始密码是 %s , 盐是: %s, 运算次数是: %d, 运算出来的密文是:%s ",password,salt,times,encodePassword);
输出:
原始密码是 123456 , 盐是: f5GQZsuWjnL9z585JjLrbQ==, 运算次数是: 2, 运算出来的密文是:55fee80f73537cefd6b3c9a920993c25
通过上面的学习,我们现在来着手搭建一个简单的使用 Shiro 进行权限验证授权的一个简单系统
pom包:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version></dependency>
application.properties文件:
#thymeleaf 配置spring.thymeleaf.mode=HTML5spring.thymeleaf.encoding=UTF-8spring.thymeleaf.servlet.content-type=text/html#缓存设置为false, 这样修改之后马上生效,便于调试spring.thymeleaf.cache=false#数据库spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.jpa.properties.hibernate.hbm2ddl.auto=update#显示SQL语句spring.jpa.show-sql=true#不加下面这句则不会默认创建MyISAM引擎的数据库spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect#自己重写的配置类,默认使用utf8编码spring.jpa.properties.hibernate.dialect=com.wmyskxz.demo.shiro.config.MySQLConfig
新建一个【entity】包,在下面创建以下实体:
用户信息:
@Entitypublic class UserInfo { @Id @GeneratedValue private Long id; // 主键. @Column(unique = true) private String username; // 登录账户,唯一. private String name; // 名称(匿名或真实姓名),用于UI显示 private String password; // 密码. private String salt; // 加密密码的盐 @JsonIgnoreProperties(value = {"userInfos"}) @ManyToMany(fetch = FetchType.EAGER) // 立即从数据库中进行加载数据 @JoinTable(name = "SysUserRole", joinColumns = @JoinColumn(name = "uid"), inverseJoinColumns = @JoinColumn(name = "roleId")) private List<SysRole> roles; // 一个用户具有多个角色 /** getter and setter */}
角色信息:
@Entitypublic class SysRole { @Id @GeneratedValue private Long id; // 主键. private String name; // 角色名称,如 admin/user private String description; // 角色描述,用于UI显示 // 角色 -- 权限关系:多对多 @JsonIgnoreProperties(value = {"roles"}) @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "permissionId")}) private List<SysPermission> permissions; // 用户 -- 角色关系:多对多 @JsonIgnoreProperties(value = {"roles"}) @ManyToMany @JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "uid")}) private List<UserInfo> userInfos;// 一个角色对应多个用户 /** getter and setter */}
权限信息:
@Entitypublic class SysPermission { @Id @GeneratedValue private Long id; // 主键. private String name; // 权限名称,如 user:select private String description; // 权限描述,用于UI显示 private String url; // 权限地址. @JsonIgnoreProperties(value = {"permissions"}) @ManyToMany @JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "permissionId")}, inverseJoinColumns = {@JoinColumn(name = "roleId")}) private List<SysRole> roles; // 一个权限可以被多个角色使用 /** getter and setter */}
注意:这里有一个坑,还缠了我蛮久感觉,就是当我们想要使用RESTful风格返回给前台JSON数据的时候,这里有一个关于多对多无限循环的坑,比如当我们想要返回给前台一个用户信息时,由于一个用户拥有多个角色,一个角色又拥有多个权限,而权限跟角色也是多对多的关系,也就是造成了 查用户→查角色→查权限→查角色→查用户… 这样的无限循环,导致传输错误,所以我们根据这样的逻辑在每一个实体类返回JSON时使用了一个@JsonIgnoreProperties注解,来排除自己对自己无线引用的过程,也就是打断这样的无限循环。
根据以上的代码会自动生成user_info(用户信息表)、sys_role(角色表)、sys_permission(权限表)、sys_user_role(用户角色表)、sys_role_permission(角色权限表)这五张表,为了方便测试我们给这五张表插入一些初始化数据:
INSERT INTO `user_info` (`id`,`name`,`password`,`salt`,`username`) VALUES (1, ‘管理员‘,‘951cd60dec2104024949d2e0b2af45ae‘, ‘xbNIxrQfn6COSYn1/GdloA==‘, ‘wmyskxz‘);INSERT INTO `sys_permission` (`id`,`description`,`name`,`url`) VALUES (1,‘查询用户‘,‘userInfo:view‘,‘/userList‘);INSERT INTO `sys_permission` (`id`,`description`,`name`,`url`) VALUES (2,‘增加用户‘,‘userInfo:add‘,‘/userAdd‘);INSERT INTO `sys_permission` (`id`,`description`,`name`,`url`) VALUES (3,‘删除用户‘,‘userInfo:delete‘,‘/userDelete‘);INSERT INTO `sys_role` (`id`,`description`,`name`) VALUES (1,‘管理员‘,‘admin‘);INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);
新建一个【config】包,在下面创建以下文件:
MySQLConfig:
public class MySQLConfig extends MySQL5InnoDBDialect { @Override public String getTableTypeString() { return "ENGINE=InnoDB DEFAULT CHARSET=utf8"; }}
这个文件关联的是配置文件中最后一个配置,是让 Hibernate 默认创建 InnoDB 引擎并默认使用 utf-8 编码
MyShiroRealm:
public class MyShiroRealm extends AuthorizingRealm { @Resource private UserInfoService userInfoService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 能进入这里说明用户已经通过验证了 UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for (SysRole role : userInfo.getRoles()) { simpleAuthorizationInfo.addRole(role.getName()); for (SysPermission permission : role.getPermissions()) { simpleAuthorizationInfo.addStringPermission(permission.getName()); } } return simpleAuthorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 获取用户输入的账户 String username = (String) authenticationToken.getPrincipal(); System.out.println(authenticationToken.getPrincipal()); // 通过username从数据库中查找 UserInfo 对象 // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 UserInfo userInfo = userInfoService.findByUsername(username); if (null == userInfo) { return null; } SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( userInfo, // 用户名 userInfo.getPassword(), // 密码 ByteSource.Util.bytes(userInfo.getSalt()), // salt=username+salt getName() // realm name ); return simpleAuthenticationInfo; }}
自定义的 Realm ,方法跟上面的认证授权过程一致
ShiroConfig:
@Configurationpublic class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/static/**", "anon"); // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); // <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); //未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 凭证匹配器 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了) * * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2); // 散列的次数,比如散列两次,相当于 md5(md5("")); return hashedCredentialsMatcher; } @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean(name = "simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError"); // 数据库异常处理 mappings.setProperty("UnauthorizedException", "403"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("ex"); // Default is "exception" //r.setWarnLogCategory("example.MvcLogger"); // No default return r; }}
Apache Shiro 的核心通过 Filter 来实现,就好像 SpringMvc 通过 DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
Filter Chain定义说明:
Shiro内置的FilterChain
Filter NameClassanonorg.apache.shiro.web.filter.authc.AnonymousFilterauthcorg.apache.shiro.web.filter.authc.FormAuthenticationFilterauthcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilterpermsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilterportorg.apache.shiro.web.filter.authz.PortFilterrestorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilterrolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFiltersslorg.apache.shiro.web.filter.authz.SslFilteruserorg.apache.shiro.web.filter.authc.UserFilter
新建【dao】包,在下面创建【UserInfoDao】接口:
public interface UserInfoDao extends JpaRepository<UserInfo, Long> { /** 通过username查找用户信息*/ public UserInfo findByUsername(String username);}
新建【service】包,创建【UserInfoService】接口:
public interface UserInfoService { /** 通过username查找用户信息;*/ public UserInfo findByUsername(String username);}
并在该包下再新建一个【impl】包,新建【UserInfoServiceImpl】实现类:
@Servicepublic class UserInfoServiceImpl implements UserInfoService { @Resource UserInfoDao userInfoDao; @Override public UserInfo findByUsername(String username) { return userInfoDao.findByUsername(username); }}
新建【controller】包,然后在下面创建以下文件:
HomeController:
@Controllerpublic class HomeController { @RequestMapping({"/","/index"}) public String index(){ return"/index"; } @RequestMapping("/login") public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{ System.out.println("HomeController.login()"); // 登录失败从request中获取shiro处理的异常信息。 // shiroLoginFailure:就是shiro异常类的全类名. String exception = (String) request.getAttribute("shiroLoginFailure"); System.out.println("exception=" + exception); String msg = ""; if (exception != null) { if (UnknownAccountException.class.getName().equals(exception)) { System.out.println("UnknownAccountException -- > 账号不存在:"); msg = "UnknownAccountException -- > 账号不存在:"; } else if (IncorrectCredentialsException.class.getName().equals(exception)) { System.out.println("IncorrectCredentialsException -- > 密码不正确:"); msg = "IncorrectCredentialsException -- > 密码不正确:"; } else if ("kaptchaValidateFailed".equals(exception)) { System.out.println("kaptchaValidateFailed -- > 验证码错误"); msg = "kaptchaValidateFailed -- > 验证码错误"; } else { msg = "else >> "+exception; System.out.println("else -- >" + exception); } } map.put("msg", msg); // 此方法不处理登录成功,由shiro进行处理 return "/login"; } @RequestMapping("/403") public String unauthorizedRole(){ System.out.println("------没有权限-------"); return "403"; }}
这里边的地址对应我们在设置 Shiro 时设置的地址
UserInfoController:
@RestControllerpublic class UserInfoController { @Resource UserInfoService userInfoService; /** * 按username账户从数据库中取出用户信息 * * @param username 账户 * @return */ @GetMapping("/userList") @RequiresPermissions("userInfo:view") // 权限管理. public UserInfo findUserInfoByUsername(@RequestParam String username) { return userInfoService.findByUsername(username); } /** * 简单模拟从数据库添加用户信息成功 * * @return */ @PostMapping("/userAdd") @RequiresPermissions("userInfo:add") public String addUserInfo() { return "addUserInfo success!"; } /** * 简单模拟从数据库删除用户成功 * * @return */ @DeleteMapping("/userDelete") @RequiresPermissions("userInfo:delete") public String deleteUserInfo() { return "deleteUserInfo success!"; }}
新建三个页面用来测试:
index.html:首页
<!DOCTYPE html><head> <meta charset="UTF-8"> <title>首页</title></head><body>index - 首页</body></html>
login.html:登录页
<!DOCTYPE html><html xmlns:th="http://www.w3.org/1999/xhtml"><head> <meta charset="UTF-8"> <title>登录页</title></head><body>错误信息:<h4 th:text="${msg}"></h4><form action="" method="post"> <p>账号:<input type="text" name="username" value="wmyskxz"/></p> <p>密码:<input type="text" name="password" value="123456"/></p> <p><input type="submit" value="登录"/></p></form></body></html>
403.html:没有权限的页面
<!DOCTYPE html><head> <meta charset="UTF-8"> <title>403错误页</title></head><body>错误页面</body></html>
注意:以上测试需要在REST工具中测试,因为在Controller层中配置了方法,大家也可以不用REST风格来测试一下看看!
标签:架构 账号 secure %s Nid 接口 nat 编写 核心
原文地址:https://www.cnblogs.com/CQqfjy/p/12400758.html