博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
springboot整合shiro(含MD5加密)
阅读量:6989 次
发布时间:2019-06-27

本文共 15283 字,大约阅读时间需要 50 分钟。

写在前面:

关于shiro介绍以及shiro整合spring,我在另一篇文章中已详细介绍,此处不作说明,请参考。下载源码。

开发环境:

1、mysql - 5.7.21

2、navicat(mysql客户端管理工具)
3、idea 2017
4、jdk9
5、tomcat 8.5
6、springboot
7、mybatis 3
8、shiro
9、maven

项目开始:

一、数据库设计:

注:数据库三张表和spring整合shiro中的一模一样,在那边已经详细说明,这里直接大家看下三张表的ER图。

img_64ce60ece6a13251a73b703d070859b6.png
图片发自简书App

二、添加依赖,配置mybatis

1、用idea新建Spring Initializr项目,项目结构如下:

img_ec4759dc4768adb9d34e9f1550f15499.png
图片发自简书App

2、添加依赖:

org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-tomcat
provided
org.springframework.boot
spring-boot-starter-test
test
org.apache.shiro
shiro-core
1.2.3
org.apache.shiro
shiro-spring
1.2.3
com.alibaba
druid
1.0.20
org.apache.commons
commons-lang3
3.4
org.springframework
spring-context-support
4.1.7.RELEASE
org.apache.tomcat.embed
tomcat-embed-jasper
javax.servlet
jstl
javax.servlet
javax.servlet-api

3、application.properties

spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql:///#spring.datasource.username=#spring.datasource.password=### mybatis ##mybatis.mapper-locations=mappers/*.xmlmybatis.type-aliases-package=com.zhu.shiro.entity## 视图解析器 ##spring.mvc.view.prefix=/pages/spring.mvc.view.suffix=.jsp

三、项目设计

注:spring整合shiro中是只有User实体类,在UserDao中定义了三个方法,通过表的关键关系查询Role和Permission;这里将采用另一种方式,三个实体类,设置实体类的关联关系。

1、entity层
User.java

public class User {    private  Integer uid;    private  String username;    private  String password;    private  Set
roles = new HashSet<>();}

Role.java

public class Role {    private  Integer rid;    private  String name;    private  Set
permissions = new HashSet<>();}

Permission.java

public class Permission {    private  Integer pid;    private  String  name;}

2、dao层

UserDao.java

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

UserDao.xml

3、service层

@Servicepublic class UserServiceImpl implements UserService {    @Autowired    private UserDao userDao;    @Override    public User findByUsername(String username) {        return userDao.findByUsername(username);    }}

4、junit测试

@RunWith(SpringRunner.class)@SpringBootTestpublic class UserServiceImplTest {    @Autowired    private UserService userService;    @Test    public void findByUsername() {        User u = userService.findByUsername("tom");        Set
roleSet = u.getRoles(); for (Role role : roleSet){ Set
permissionSet = role.getPermissions(); for (Permission permission : permissionSet){ System.out.println(permission.getName()); } System.out.println(role.getName()); } }}

运行结果:

img_c0f86ebc027bc9dd74f90df9f7ca11f3.png
图片发自简书App
数据库中tom是admin角色,有增删改查权限,符合预期,测试通过。

5、controller层

TestController.java

@Controllerpublic class TestController {    //用户登录    @RequestMapping("/loginUser")    public String loginUser(@RequestParam("username") String username,                            @RequestParam("password") String password,                            HttpSession session) {        //把前端输入的username和password封装为token        UsernamePasswordToken token = new UsernamePasswordToken(username, password);        Subject subject = SecurityUtils.getSubject();        try {            subject.login(token);            session.setAttribute("user", subject.getPrincipal());            return "index";        } catch (Exception e) {            return "login";        }    }    //退出登录    @RequestMapping("/logout")    public String logout() {        Subject subject = SecurityUtils.getSubject();        if (subject != null) {            subject.logout();        }        return "login";    }    //访问login时跳到login.jsp    @RequestMapping("/login")    public String login() {        return "login";    }    //admin角色才能访问    @RequestMapping("/admin")    @ResponseBody    public String admin() {        return "admin success";    }    //有delete权限才能访问    @RequestMapping("/edit")    @ResponseBody    public String edit() {        return "edit success";    }    @RequestMapping("/test")    @ResponseBody    @RequiresRoles("guest")    public String test(){        return "test success";    }}

说明:这里用户登录方法用到了shiro,但是这里还没配置shiro,所以暂时不能使用,先搭起整个骨架,然后再加入shiro。

6、jsp页面

login.jsp
(登录页面)

%@ page contentType="text/html;charset=UTF-8" language="java" %>    Login   

欢迎登录!

index.jsp

(登录成功跳转的页面)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>    Title   

欢迎登录,${user.username}

unauthorized.jsp

(无权访问跳转的页面)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>    unauthorizedunauthorized!

现在说一下要求:

admin路由要求只有具有admin角色的用户才能访问,edit路由需要有delete权限的用户才能访问,test路由要guest角色才能访问,login、loginUser都不做拦截,本文讲解两种拦截方式,对test的拦截是在controller对应的方法上加注解,其他是拦截是写在shiro的配置类中。
预期分析:
tom是有admin角色和所有权限,所以用tom登录后,可以访问edit和admin,但是不能访问guest;而cat是guest角色,只有create和query权限,所以不能访问admin和edit,但是可以访问guest。

四、配置shiro

由于springboot还没有集成shiro,所以不能直接在application.properties中配置,需要通过类的方式配置。

核心配置类:

ShiroConfiguration.java

@Configurationpublic class ShiroConfiguration {    /**     * 密码校验规则HashedCredentialsMatcher     * 这个类是为了对密码进行编码的 ,     * 防止密码在数据库里明码保存 , 当然在登陆认证的时候 ,     * 这个类也负责对form里输入的密码进行编码     * 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher     */    @Bean("hashedCredentialsMatcher")    public HashedCredentialsMatcher hashedCredentialsMatcher() {        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();        //指定加密方式为MD5        credentialsMatcher.setHashAlgorithmName("MD5");        //加密次数        credentialsMatcher.setHashIterations(1024);        credentialsMatcher.setStoredCredentialsHexEncoded(true);        return credentialsMatcher;    }    @Bean("authRealm")    @DependsOn("lifecycleBeanPostProcessor")//可选    public AuthRealm authRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {        AuthRealm authRealm = new AuthRealm();        authRealm.setAuthorizationCachingEnabled(false);        authRealm.setCredentialsMatcher(matcher);        return authRealm;    }    /**     * 定义安全管理器securityManager,注入自定义的realm     * @param authRealm     * @return     */    @Bean("securityManager")    public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) {        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();        manager.setRealm(authRealm);        return manager;    }    /**     * 定义shiroFilter过滤器并注入securityManager     * @param manager     * @return     */    @Bean("shiroFilter")    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();        //设置securityManager        bean.setSecurityManager(manager);        //设置登录页面        //可以写路由也可以写jsp页面的访问路径        bean.setLoginUrl("/login");        //设置登录成功跳转的页面        bean.setSuccessUrl("/pages/index.jsp");        //设置未授权跳转的页面        bean.setUnauthorizedUrl("/pages/unauthorized.jsp");        //定义过滤器        LinkedHashMap
filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/index", "authc"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/loginUser", "anon"); filterChainDefinitionMap.put("/admin", "roles[admin]"); filterChainDefinitionMap.put("/edit", "perms[delete]"); filterChainDefinitionMap.put("/druid/**", "anon"); //需要登录访问的资源 , 一般将/**放在最下边 filterChainDefinitionMap.put("/**", "authc"); bean.setFilterChainDefinitionMap(filterChainDefinitionMap); return bean; } /** * Spring的一个bean , 由Advisor决定对哪些类的方法进行AOP代理 . * @return */ @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } /** * 配置shiro跟spring的关联 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } /** * lifecycleBeanPostProcessor是负责生命周期的 , 初始化和销毁的类 * (可选) */ @Bean("lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); }}

注:这个类每个bean的作用都已在代码中注释说明,这个类就相当于spring整合shiro的spring-shiro.xml中对shiro的配置。

自定义realm:

AutuRealm.java

public class AuthRealm extends AuthorizingRealm{    @Autowired    private UserService userService;    /**     * 为用户授权     * @param principals     * @return     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {        //获取前端输入的用户信息,封装为User对象        User userweb = (User) principals.getPrimaryPrincipal();        //获取前端输入的用户名        String username = userweb.getUsername();        //根据前端输入的用户名查询数据库中对应的记录        User user = userService.findByUsername(username);        //如果数据库中有该用户名对应的记录,就进行授权操作        if (user != null){            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();            //因为addRoles和addStringPermissions方法需要的参数类型是Collection            //所以先创建两个collection集合            Collection
rolesCollection = new HashSet
(); Collection
perStringCollection = new HashSet
(); //获取user的Role的set集合 Set
roles = user.getRoles(); //遍历集合 for (Role role : roles){ //将每一个role的name装进collection集合 rolesCollection.add(role.getName()); //获取每一个Role的permission的set集合 Set
permissionSet = role.getPermissions(); //遍历集合 for (Permission permission : permissionSet){ //将每一个permission的name装进collection集合 perStringCollection.add(permission.getName()); } //为用户授权 info.addStringPermissions(perStringCollection); } //为用户授予角色 info.addRoles(rolesCollection); return info; }else{ return null; } } /** * 认证登录 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //token携带了用户信息 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; //获取前端输入的用户名 String userName = usernamePasswordToken.getUsername(); //根据用户名查询数据库中对应的记录 User user = userService.findByUsername(userName); //当前realm对象的name String realmName = getName(); //盐值 ByteSource credentialsSalt = ByteSource.Util.bytes(user.getUsername()); //封装用户信息,构建AuthenticationInfo对象并返回 AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, realmName); return authcInfo; }}

注:这个类也有详细的注释说明。

这样就完成了springboot对shiro的整合,接下来就可以进行测试了!

五、测试

tom登录

img_5e58cb414ec1ef36eac2bcd752329587.png
图片发自简书App
img_8ce7362f1211435575762c047b3bc65f.png
图片发自简书App

tom访问admin

img_773587e670688bff6d6e0ed5167a63db.png
图片发自简书App

tom访问test

img_a6188438a511c4d17cb626ce146c4cbe.png
图片发自简书App

cat登录

img_e6dac00bf97feb8f6a0bc67fa4667eae.png
图片发自简书App

cat访问admin

img_c6e3e48398af8f814a5351669e076eec.png
图片发自简书App

cat访问test

img_cd614e3d79745ce4004027fe20202bd3.png
图片发自简书App

测试结果与预期相符,测试通过,springboot整合shiro成功!

特别说明:

由于设置了MD5加密,所以数据库中存储的用户密码应该是加密后的密文,否则在登录页面输入明文会验证不通过。假如1234的密文为asdfghjkl,数据库中存储的应该是asdfghjkl,在登录时输入1234就能验证通过。

附上明文转密文的代码:

public static void main(String[] args) {        String hashAlgorithName = "MD5";        String password = "登录时输入的密码";        int hashIterations = 1024;//加密次数        ByteSource credentialsSalt = ByteSource.Util.bytes("登录时输入的用户名");        Object obj = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations);        System.out.println(obj);    }

若不使用MD5加密

1、添加一个类

public class CredenttiaMatcher extends SimpleCredentialsMatcher{    @Override    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;        String password = new String(usernamePasswordToken.getPassword());        String dbPassword = (String) info.getCredentials();        return this.equals(password,dbPassword);    }}

2、将ShiroConfiguration.java中名为"hashedCredentialsMatcher"的bean替换成:

*@Bean("credenttiaMatcher")    public CredenttiaMatcher credenttiaMatcher() {        return new CredenttiaMatcher();    }

将名为"authRealm"的bean替换成:

@Bean("authRealm")    @DependsOn("lifecycleBeanPostProcessor")//可选    public AuthRealm authRealm(@Qualifier("credenttiaMatcher") CredenttiaMatcher matcher) {        AuthRealm authRealm = new AuthRealm();        authRealm.setCredentialsMatcher(matcher);        return authRealm;    }

3、AuthRealm.java中的doGetAuthenticationInfo方法里面的内容替换成:

//=========================未加密版==========================        //token携带了用户登录的信息         UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;        //获取前端输入的用户名        String username  = usernamePasswordToken.getUsername();        //根据前端输入的用户名查询数据库中的记录         User user = userService.findByUsername(username);        //校验密码,验证登录         return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());

完成以上3步就去掉了MD5加密。

以上内容属于个人学习笔记整理,如有错误,欢迎批评指正!

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:

转载地址:http://ipzvl.baihongyu.com/

你可能感兴趣的文章
MATLAB编程与应用系列-第2章 数组及矩阵的创建及操作(2)
查看>>
一步一步实现web程序信息管理系统之二----后台框架实现跳转登陆页面
查看>>
接口和抽象类是否继承了Object
查看>>
小白都能看懂的Linux系统下安装配置Zabbix
查看>>
服务器被传***怎么办
查看>>
ios获取设备信息总结
查看>>
如何避免「温水煮码农」?
查看>>
AJPFX总结内部类
查看>>
Spring Boot Security 整合 JWT 实现 无状态的分布式API接口
查看>>
指向结构体变量的指针作函数参数
查看>>
MR程序重要组件-combiner
查看>>
HDFS 文件操作命令(2)
查看>>
深入理解typedef
查看>>
日期加减
查看>>
phpexcel 内存溢出 优化
查看>>
Haproxy+Keepalived主主高可用
查看>>
用java从hadoop中读数据
查看>>
实现首页分类栏按样式循环显示
查看>>
ActiveMQ 初试
查看>>
log4j xml 配置 自定义使用版
查看>>