目录
在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。
市面上存在比较有名的:Shiro,Spring Security !
这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?
Spring Security官网地址:Spring Security
首先我们看下它的官网介绍:
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
中文翻译:
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求
从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。
怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。
Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。
实验环境搭建
链接:https://pan.baidu.com/s/1L6dDQYI4RNyC9KFWl6beTw
提取码:63ib

将静态资源放到对应位置

spring.thymeleaf.cache=false
- @Controller
- public class RouterController {
-
- @RequestMapping({"/","/index"})
- public String index(){
- return "index";
- }
-
- @RequestMapping("/toLogin")
- public String toLogin(){
- return "views/login";
- }
-
- @RequestMapping("/level1/{id}")
- public String level1(@PathVariable("id") int id){
- return "views/level1/"+id;
- }
-
- @RequestMapping("/level2/{id}")
- public String level2(@PathVariable("id") int id){
- return "views/level2/"+id;
- }
-
- @RequestMapping("/level3/{id}")
- public String level3(@PathVariable("id") int id){
- return "views/level3/"+id;
- }
-
- }

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
WebSecurityConfigurerAdapter:自定义Security策略
AuthenticationManagerBuilder:自定义认证策略
@EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在。
目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-securityartifactId>
- dependency>
参考官网:Spring Security
- @EnableWebSecurity // 开启WebSecurity模式
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
- @Override
- protected void configure(HttpSecurity http) throws Exception {
-
- }
- }
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- // 定制请求的授权规则
- // 首页所有人可以访问,功能也对应有权限的人才能访问
- http.authorizeRequests().antMatchers("/").permitAll()
- .antMatchers("/level1/**").hasRole("vip1")
- .antMatchers("/level2/**").hasRole("vip2")
- .antMatchers("/level3/**").hasRole("vip3");
- }
- // 开启自动配置的登录功能
- // /login 请求来到登录页
- // /login?error 重定向到这里表示登录失败
- http.formLogin();

- //定义认证规则
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
-
- //在内存中定义,也可以在jdbc中去拿....
- auth.inMemoryAuthentication()
- .withUser("shuangma").password("123456").roles("vip2","vip3")
- .and()
- .withUser("root").password("123456").roles("vip1","vip2","vip3")
- .and()
- .withUser("guest").password("123456").roles("vip1");
- }
There is no PasswordEncoder mapped for the id “null”
- //定义认证规则
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- //在内存中定义,也可以在jdbc中去拿....
- //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
- //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
- //spring security 官方推荐的是使用bcrypt加密方式。
-
- auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
- .withUser("shuangma").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
- .and()
- .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
- .and()
- .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
- }
- //定制请求的授权规则
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- //....
- //开启自动配置的注销的功能
- // /logout 注销请求 默认隐式会跳转到自动配置的登录页
- http.logout();
- }
- <a class="item" th:href="@{/logout}">
- <i class="sign-out icon">i> 注销
- a>
- // .logoutSuccessUrl("/"); 注销成功来到首页
- http.logout().logoutSuccessUrl("/");
我们需要结合thymeleaf中的一些功能
sec:authorize="isAuthenticated()":是否认证登录!来显示不同的页面
Maven依赖:
- <dependency>
- <groupId>org.thymeleaf.extrasgroupId>
- <artifactId>thymeleaf-extras-springsecurity5artifactId>
- <version>3.0.4.RELEASEversion>
- dependency>
导入命名空间
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
修改导航栏,增加认证判断
- <div class="right menu">
-
-
- <div sec:authorize="!isAuthenticated()">
- <a class="item" th:href="@{/toLogin}">
- <i class="address card icon">i> 登录
- a>
- div>
-
-
- <div sec:authorize="isAuthenticated()">
- <a class="item">
- <i class="address card icon">i>
-
- 用户名:<span sec:authentication="principal.username">span>
- 角色:<span sec:authentication="principal.authorities">span>
- a>
- div>
-
- <div sec:authorize="isAuthenticated()">
- <a class="item" th:href="@{/logout}">
- <i class="address card icon">i> 注销
- a>
- div>
- div>
http.csrf().disable();http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
- <div class="column" sec:authorize="hasRole('vip1')">
- <div class="ui raised segment">
- <div class="ui">
- <div class="content">
- <h5 class="content">Level 1h5>
- <hr>
- <div><a th:href="@{/level1/1}"><i class="bullhorn icon">i> Level-1-1a>div>
- <div><a th:href="@{/level1/2}"><i class="bullhorn icon">i> Level-1-2a>div>
- <div><a th:href="@{/level1/3}"><i class="bullhorn icon">i> Level-1-3a>div>
- div>
- div>
- div>
- div>
-
- <div class="column" sec:authorize="hasRole('vip2')">
- <div class="ui raised segment">
- <div class="ui">
- <div class="content">
- <h5 class="content">Level 2h5>
- <hr>
- <div><a th:href="@{/level2/1}"><i class="bullhorn icon">i> Level-2-1a>div>
- <div><a th:href="@{/level2/2}"><i class="bullhorn icon">i> Level-2-2a>div>
- <div><a th:href="@{/level2/3}"><i class="bullhorn icon">i> Level-2-3a>div>
- div>
- div>
- div>
- div>
-
- <div class="column" sec:authorize="hasRole('vip3')">
- <div class="ui raised segment">
- <div class="ui">
- <div class="content">
- <h5 class="content">Level 3h5>
- <hr>
- <div><a th:href="@{/level3/1}"><i class="bullhorn icon">i> Level-3-1a>div>
- <div><a th:href="@{/level3/2}"><i class="bullhorn icon">i> Level-3-2a>div>
- <div><a th:href="@{/level3/3}"><i class="bullhorn icon">i> Level-3-3a>div>
- div>
- div>
- div>
- div>
现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单
- //定制请求的授权规则
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- //。。。。。。。。。。。
- //记住我 cookie 默认保存两周
- http.rememberMe();
- }
思考:如何实现的呢?其实非常简单
我们可以查看浏览器的cookie
![]()
现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?
http.formLogin().loginPage("/toLogin");
- <div sec:authorize="!isAuthenticated()">
- <a class="item" th:href="@{/toLogin}">
- <i class="address card icon">i> 登录
- a>
- div>
- <form th:action="@{/toLogin}" method="post">
- <div class="field">
- <label>Usernamelabel>
- <div class="ui left icon input">
- <input type="text" placeholder="Username" name="username">
- <i class="user icon">i>
- div>
- div>
- <div class="field">
- <label>Passwordlabel>
- <div class="ui left icon input">
- <input type="password" name="password">
- <i class="lock icon">i>
- div>
- div>
- <input type="submit" class="ui blue submit button"/>
- form>
如果我们非要让他的跳转/login的页面,但是我们没有这个页面怎么办呢?
- http.formLogin()
- .loginPage("/toLogin")
- .loginProcessingUrl("/login"); // 登陆表单提交请求
- http.formLogin()
- .usernameParameter("username")//这个地方可以改为前端定义的任何名字
- .passwordParameter("password")//这个地方可以改为前端定义的任何名字
- .loginPage("/toLogin")
- .loginProcessingUrl("/login"); // 登陆表单提交请求
- <div class="field">
- <input type="checkbox" name="remember"> 记住我
- div>
- //定制记住我的参数!
- http.rememberMe().rememberMeParameter("remember");
- @EnableWebSecurity // 开启WebSecurity模式
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- //授权
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- // 定制请求的授权规则
- // 首页所有人可以访问,功能也对应有权限的人才能访问
- http.authorizeRequests().antMatchers("/").permitAll()
- .antMatchers("/level1/**").hasRole("vip1")
- .antMatchers("/level2/**").hasRole("vip2")
- .antMatchers("/level3/**").hasRole("vip3");
-
- // 开启自动配置的登录功能
- // /login 请求来到登录页
- // /login?error 重定向到这里表示登录失败
- http.formLogin()
- .usernameParameter("username")//这个地方可以改为前端定义的任何名字
- .passwordParameter("password")//这个地方可以改为前端定义的任何名字
- .loginPage("/toLogin")
- .loginProcessingUrl("/login"); // 登陆表单提交请求
-
- //开启自动配置的注销的功能
- // /logout 注销请求默认隐式会跳转到自动配置的登录页
- // .logoutSuccessUrl("/"); 注销成功来到首页
- http.logout().logoutSuccessUrl("/");
- http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
- //记住我 cookie 默认保存两周
- //定制记住我的参数!
- http.rememberMe().rememberMeParameter("remember");
- }
- //定义认证规则
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- //在内存中定义,也可以在jdbc中去拿....
- //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
- //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
- //spring security 官方推荐的是使用bcrypt加密方式。
-
- auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
- .withUser("shuangma").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
- .and()
- .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
- .and()
- .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
- }
- }
Shiro 是什么?
apche Shiro 是 Java 的一个安全(权限)框架。
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。
Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。
下载地址
有哪些功能?

Shiro架构(外部)
从外部来看Shiro,即从应用程序角度的来观察如何使用Shiro完成工作

Shiro架构(内部)

快速实践
1.创建一个maven父工程,用来学习Shiro,删掉不必要的部分
2.创建一个普通的Maven子工程:hell-shiro

- <dependencies>
- <dependency>
- <groupId>org.apache.shirogroupId>
- <artifactId>shiro-coreartifactId>
- <version>1.10.0version>
- dependency>
-
-
- <dependency>
- <groupId>org.slf4jgroupId>
- <artifactId>jcl-over-slf4jartifactId>
- <version>2.0.3version>
- dependency>
- <dependency>
- <groupId>org.slf4jgroupId>
- <artifactId>slf4j-log4j12artifactId>
- <version>2.0.3version>
- dependency>
- <dependency>
- <groupId>log4jgroupId>
- <artifactId>log4jartifactId>
- <version>1.2.17version>
- dependency>
- dependencies>
log4j.properties
- log4j.rootLogger=INFO, stdout
-
- log4j.appender.stdout=org.apache.log4j.ConsoleAppender
- log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
- log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
-
- # General Apache libraries
- log4j.logger.org.apache=WARN
-
- # Spring
- log4j.logger.org.springframework=WARN
-
- # Default Shiro logging
- log4j.logger.org.apache.shiro=INFO
-
- # Disable verbose logging
- log4j.logger.org.apache.shiro.util.ThreadContext=WARN
- log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro.ini
- [users]
- # user 'root' with password 'secret' and the 'admin' role
- root = secret, admin
- # user 'guest' with the password 'guest' and the 'guest' role
- guest = guest, guest
- # user 'presidentskroob' with password '12345' ("That's the same combination on
- # my luggage!!!" ;)), and role 'president'
- presidentskroob = 12345, president
- # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
- darkhelmet = ludicrousspeed, darklord, schwartz
- # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
- lonestarr = vespa, goodguy, schwartz
-
- # -----------------------------------------------------------------------------
- # Roles with assigned permissions
- #
- # Each line conforms to the format defined in the
- # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
- # -----------------------------------------------------------------------------
- [roles]
- # 'admin' role has all permissions, indicated by the wildcard '*'
- admin = *
- # The 'schwartz' role can do anything (*) with any lightsaber:
- schwartz = lightsaber:*
- # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
- # license plate 'eagle5' (instance specific id)
- goodguy = winnebago:drive:eagle5
这里需要安装一个idea的插件,代码就会高亮
- /*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.authc.*;
- import org.apache.shiro.config.IniSecurityManagerFactory;
- import org.apache.shiro.mgt.SecurityManager;
- import org.apache.shiro.session.Session;
- import org.apache.shiro.subject.Subject;
- import org.apache.shiro.util.Factory;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
-
- /**
- * Simple Quickstart application showing how to use Shiro's API.
- * 简单入门Shiro使用API
- *
- * @since 0.9 RC2
- */
- public class Quickstart {
-
- private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
-
-
- public static void main(String[] args) {
-
- // The easiest way to create a Shiro SecurityManager with configured
- // realms, users, roles and permissions is to use the simple INI config.
- // We'll do that by using a factory that can ingest a .ini file and
- // return a SecurityManager instance:
-
- // Use the shiro.ini file at the root of the classpath
- // (file: and url: prefixes load from files and urls respectively):
- // 读取配置文件:
- Factory
factory = new IniSecurityManagerFactory("classpath:shiro.ini"); - SecurityManager securityManager = factory.getInstance();
-
- // for this simple example quickstart, make the SecurityManager
- // accessible as a JVM singleton. Most applications wouldn't do this
- // and instead rely on their container configuration or web.xml for
- // webapps. That is outside the scope of this simple quickstart, so
- // we'll just do the bare minimum so you can continue to get a feel
- // for things.
- SecurityUtils.setSecurityManager(securityManager);
-
- // Now that a simple Shiro environment is set up, let's see what you can do:
-
- // get the currently executing user:
- // 获取当前的用户对象 Subject
- Subject currentUser = SecurityUtils.getSubject();
-
- // Do some stuff with a Session (no need for a web or EJB container!!!)
- //通过当前用户拿到Shiro的Session 可以脱离web存值取值
- Session session = currentUser.getSession();
- session.setAttribute("someKey", "aValue");
- String value = (String) session.getAttribute("someKey");
- if (value.equals("aValue")) {
- log.info("Retrieved the correct value! [" + value + "]");
- }
-
- // let's login the current user so we can check against roles and permissions:
- //判断当前的用户是否被认证
- if (!currentUser.isAuthenticated()) {
- //Token 令牌
- UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
- //设置记住我
- token.setRememberMe(true);
- try {
- //执行登录操作
- currentUser.login(token);
- } catch (UnknownAccountException uae) {
- log.info("There is no user with username of " + token.getPrincipal());
- } catch (IncorrectCredentialsException ice) {
- log.info("Password for account " + token.getPrincipal() + " was incorrect!");
- } catch (LockedAccountException lae) {
- log.info("The account for username " + token.getPrincipal() + " is locked. " +
- "Please contact your administrator to unlock it.");
- }
- // ... catch more exceptions here (maybe custom ones specific to your application?
- catch (AuthenticationException ae) {
- //unexpected condition? error?
- }
- }
-
- //say who they are:
- //print their identifying principal (in this case, a username):
- log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
-
- //test a role:
- // 检查角色
- if (currentUser.hasRole("schwartz")) {
- log.info("May the Schwartz be with you!");
- } else {
- log.info("Hello, mere mortal.");
- }
-
- //test a typed permission (not instance-level)
- //粗粒度
- if (currentUser.isPermitted("lightsaber:wield")) {
- log.info("You may use a lightsaber ring. Use it wisely.");
- } else {
- log.info("Sorry, lightsaber rings are for schwartz masters only.");
- }
-
- //a (very powerful) Instance Level permission:
- //细粒度
- if (currentUser.isPermitted("winnebago:drive:eagle5")) {
- log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
- "Here are the keys - have fun!");
- } else {
- log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
- }
-
- //all done - log out!
- //注销
- currentUser.logout();
-
- //结束
- System.exit(0);
- }
- }
源码剖析
-
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.authc.*;
- import org.apache.shiro.config.IniSecurityManagerFactory;
- import org.apache.shiro.mgt.SecurityManager;
- import org.apache.shiro.session.Session;
- import org.apache.shiro.subject.Subject;
- import org.apache.shiro.util.Factory;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- public class Quickstart {
-
- //使用log输出
- private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
-
-
- public static void main(String[] args) {
-
-
- // 读取配置文件:
- Factory
factory = new IniSecurityManagerFactory("classpath:shiro.ini"); - SecurityManager securityManager = factory.getInstance();
- SecurityUtils.setSecurityManager(securityManager);
-
- // Now that a simple Shiro environment is set up, let's see what you can do:
- //现在已经设置了一个简单的Shiro环境,让我们看看可以做些什么:
-
- // get the currently executing user:
- // 获取当前的用户对象 Subject
- Subject currentUser = SecurityUtils.getSubject();
-
- // Do some stuff with a Session (no need for a web or EJB container!!!)
- //通过当前用户拿到Shiro的Session 可以脱离web存值取值
- Session session = currentUser.getSession();
- session.setAttribute("someKey", "aValue");
- String value = (String) session.getAttribute("someKey");
- if (value.equals("aValue")) {
- log.info("Subject=>session! [" + value + "]");
- }
-
- // let's login the current user so we can check against roles and permissions:
- //判断当前的用户是否被认证
- if (!currentUser.isAuthenticated()) {
- //Token 令牌
- UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
- //设置记住我
- token.setRememberMe(true);
- try {
- //执行登录操作
- currentUser.login(token);
- //用户名错误
- } catch (UnknownAccountException uae) {
- log.info("There is no user with username of " + token.getPrincipal());
- //密码错误
- } catch (IncorrectCredentialsException ice) {
- log.info("Password for account " + token.getPrincipal() + " was incorrect!");
- //用户被锁定
- } catch (LockedAccountException lae) {
- log.info("The account for username " + token.getPrincipal() + " is locked. " +
- "Please contact your administrator to unlock it.");
- }
- // ... catch more exceptions here (maybe custom ones specific to your application?
- //在这里捕获更多异常(可能是特定于您的应用程序的自定义异常?
- catch (AuthenticationException ae) {
- //unexpected condition? error?
- //意想不到的条件? 错误呢?
- }
- }
-
- //say who they are:
- //print their identifying principal (in this case, a username):
- //获取当前用户信息
- log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
-
- //test a role:
- // 检查角色 是否有这个角色
- if (currentUser.hasRole("schwartz")) {
- log.info("May the Schwartz be with you!");
- } else {
- log.info("Hello, mere mortal.");
- }
-
- //test a typed permission (not instance-level)
- //粗粒度
- if (currentUser.isPermitted("lightsaber:wield")) {
- log.info("You may use a lightsaber ring. Use it wisely.");
- } else {
- log.info("Sorry, lightsaber rings are for schwartz masters only.");
- }
-
- //a (very powerful) Instance Level permission:
- //细粒度
- if (currentUser.isPermitted("winnebago:drive:eagle5")) {
- log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
- "Here are the keys - have fun!");
- } else {
- log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
- }
-
- //all done - log out!
- //注销
- currentUser.logout();
-
- //结束
- System.exit(0);
- }
- }
在主启动类同级新建一个controller包,编写controller
- @Controller
- public class MyController {
-
- @RequestMapping({"/","/index"})
- public String toIndex(Model model) {
- model.addAttribute("msg","hello,Shiro");
- return "index";
- }
-
- @RequestMapping("/user/add")
- public String add() {
- return "user/add";
- }
-
- @RequestMapping("/user/update")
- public String update() {
- return "user/update";
- }
- }
在templates包新建一个index.html页面
- html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>首页title>
- head>
- <body>
- <div>
- <h1>首页h1>
- <p th:text="${msg}">p>
-
- <hr>
- <a th:href="@{/user/add}">adda> | <a th:href="@{/user/update}">updatea>
- div>
- body>
- html>
在templates包下新建user包,新建一个add.html页面
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Titletitle>
- head>
- <body>
- <h1>addh1>
- body>
- html>
在user包新建一个update.html页面
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Titletitle>
- head>
- <body>
- <h1>updateh1>
- body>
- html>
项目结构
-
- <dependency>
- <groupId>org.apache.shirogroupId>
- <artifactId>shiro-springartifactId>
- <version>1.10.0version>
- dependency>
自定义类UserRealm
- //自定义的UserRealm
- public class UserRealm extends AuthorizingRealm {
- //授权
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
- System.out.println("执行了=>授权doGetAuthorizationInfo");
- return null;
- }
-
- //认证
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
- System.out.println("执行了=>认证doGetAuthorizationInfo");
- return null;
- }
- }
编写配置ShiroConfig
- 1.创建realm对象,需要自定义类
- 2.DefaultWebSecurityManager
- 3.ShiroFilterFactoryBean
- @Configuration
- public class ShiroConfig {
-
- //3. shiroFilterFactoryBean
-
- @Bean
- public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
- ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
- // 设置安全管理器
- bean.setSecurityManager(defaultWebSecurityManager);
-
- return bean;
- }
-
- //2. DefaultWebSecurityManager
-
- @Bean
- public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
-
- // 关联userRealm
- securityManager.setRealm(userRealm);
- return securityManager;
- }
- //1. 创建realm对象,需要自定义类
-
- @Bean
- public UserRealm userRealm() {
- return new UserRealm();
- }
- }
ShiroConfig中的getShiroFilterFactoryBean方法中添加如下配置
- Map
filterMap = new LinkedHashMap<>(); - filterMap.put("/user/add","authc");
- filterMap.put("/user/update","authc");
- bean.setFilterChainDefinitionMap(filterMap);
点击首页的add或者update之后

- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>登录页面title>
- head>
- <body>
- <h1>登录h1>
- <hr>
-
- <form action="">
- <p>用户名:<input type="text" name="username">p>
- <p>密码:<input type="text" name="password">p>
- <p><input type="submit">p>
- form>
- body>
- html>
- @RequestMapping("/toLogin")
- public String toLogin() {
- return "login";
- }
ShiroConfig中的getShiroFilterFactoryBean方法中添加如下配置- //设置登录的请求
- bean.setLoginUrl("/toLogin");
1.在MyController中编写用户提交表单之后处理
- @RequestMapping("/login")
- public String login(String username, String password, Model model) {
- //获取一个用户
- Subject subject = SecurityUtils.getSubject();
- // 封装用户的登录数据
- UsernamePasswordToken token = new UsernamePasswordToken(username, password);
-
- try {
- subject.login(token);//执行登录的方法,如果没有异常就说明ok了
- return "index";
- } catch (UnknownAccountException e) {//用户名不存在
- model.addAttribute("msg","用户名错误");
- return "login";
- } catch (IncorrectCredentialsException e) {//密码不存在
- model.addAttribute("msg","密码错误");
- return "login";
- }
-
- }
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>登录页面title>
- head>
- <body>
- <h1>登录h1>
- <hr>
- <p th:text="${msg}" style="color: red;">p>
- <form th:action="@{/login}">
- <p>用户名:<input type="text" name="username">p>
- <p>密码:<input type="text" name="password">p>
- <p><input type="submit">p>
- form>
- body>
- html>
3.测试
UserRealm中的认证(doGetAuthenticationInfo)- //认证
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- System.out.println("执行了=>认证doGetAuthorizationInfo");
- // 用户名、密码, 数据中取
- String name = "root";
- String password = "123456";
-
- UsernamePasswordToken userToken = (UsernamePasswordToken)token;
-
- if (!userToken.getUsername().equals(name)) {
- return null;//抛出异常 UnknownAccountException
- }
-
- // 密码认证,shiro做
- return new SimpleAuthenticationInfo("",password,"");
- }
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- dependency>
- <dependency>
- <groupId>log4jgroupId>
- <artifactId>log4jartifactId>
- <version>1.2.17version>
- dependency>
- <dependency>
- <groupId>com.alibabagroupId>
- <artifactId>druidartifactId>
- <version>1.2.11version>
- dependency>
- <dependency>
- <groupId>org.mybatis.spring.bootgroupId>
- <artifactId>mybatis-spring-boot-starterartifactId>
- <version>2.2.2version>
- dependency>
- spring:
- datasource:
- username: root
- password: "000000"
- #?serverTimezone=UTC解决时区的报错
- url: jdbc:mysql://localhost:3306/springboot_jdbc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
- driver-class-name: com.mysql.cj.jdbc.Driver
- type: com.alibaba.druid.pool.DruidDataSource
-
- #Spring Boot 默认是不注入这些属性值的,需要自己绑定
- #druid 数据源专有配置
- initialSize: 5
- minIdle: 5
- maxActive: 20
- maxWait: 60000
- timeBetweenEvictionRunsMillis: 60000
- minEvictableIdleTimeMillis: 300000
- validationQuery: SELECT 1 FROM DUAL
- testWhileIdle: true
- testOnBorrow: false
- testOnReturn: false
- poolPreparedStatements: true
-
- #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
- #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
- #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
- filters: stat,wall,log4j
- maxPoolPreparedStatementPerConnectionSize: 20
- useGlobalDataSourceStat: true
- connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
-
- mybatis:
- type-aliases-package: com.jiang.pojo
- mapper-locations: classpath:mapper/*.xml
- public class User {
- private int id;
- private String name;
- private String pwd;
- //有参无参、get、set、toStiring
- }
- //@Mapper : 表示本类是一个 MyBatis 的 Mapper接口
- @Mapper
- @Repository
- //@MapperScan("com.jiang.mapper")//可以在主程序写入这个注解,用来扫描包
- public interface UserMapper {
- User queryUserByName(String name);
-
- }
- "1.0" encoding="UTF-8" ?>
- mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.jiang.mapper.UserMapper">
- <select id="queryUserByName" parameterType="String" resultType="com.jiang.pojo.User">
- select * from user where `name`= #{name}
- select>
- mapper>
- public interface UserService {
-
- User queryUserByName(String name);
- }
- @Service
- public class UserServiceImpl implements UserService {
-
- @Autowired
- UserMapper userMapper;
-
- @Override
- public User queryUserByName(String name) {
- return userMapper.queryUserByName(name);
- }
- }
- @SpringBootTest
- class ShiroSpringbootApplicationTests {
-
- @Autowired
- UserService userService;
- @Test
- void contextLoads() {
- System.out.println(userService.queryUserByName("狂神"));
- }
-
- }
UserRealm连接真实数据库- @Autowired
- UserService userService;
- //认证
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- System.out.println("执行了=>认证doGetAuthorizationInfo");
-
- UsernamePasswordToken userToken = (UsernamePasswordToken) token;
-
- // 真实数据库 用户名、密码, 数据中取
- User user = userService.queryUserByName(userToken.getUsername());
-
- if (user == null) {//没有这个人
- return null;
- }
-
- // 密码认证,shiro做
- return new SimpleAuthenticationInfo("",user.getPwd(),"");
- }
打断点Debug

SimpleCredentialsMatcher加密
1.ShiroConfig中的getShiroFilterFactoryBean方法添加认证代码- //授权,正常情况下,没有授权会跳转到为授权页面
- filterMap.put("/user/add","perms[user:add]");
- filterMap.put("/user/update","perms[user:update]");

MyController
- @RequestMapping("/noauto")
- @ResponseBody
- public String unauthorized() {
- return "未经授权,无法访问此页面";
- }
ShiroConfig中的getShiroFilterFacoryBean方法中添加
- //未授权页面
- bean.setUnauthorizedUrl("/noauto");
所以需要在UserRealm中为用户进行真正授权


- public class User {
- private int id;
- private String name;
- private String pwd;
- private String perms;
- //getset、有参无参、toString
- }
- //自定义的UserRealm
- public class UserRealm extends AuthorizingRealm {
-
- @Autowired
- UserService userService;
- //授权
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
- System.out.println("执行了=>授权doGetAuthorizationInfo");
-
- SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
-
- //拿到当前登录的这个对象
- Subject subject = SecurityUtils.getSubject();
- User currentUser = (User)subject.getPrincipal();//拿到user对象
-
- //设置当前用户的权限
- info.addStringPermission(currentUser.getPerms());
-
- return info;
- }
-
- //认证
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- ......
- // 密码认证,shiro做
- return new SimpleAuthenticationInfo(user,user.getPwd(),"");
- }
- }
-
- <dependency>
- <groupId>com.github.theborakompanionigroupId>
- <artifactId>thymeleaf-extras-shiroartifactId>
- <version>2.1.0version>
- dependency>
- // 整合ShiroDialect: 用来整合 Shiro thymeleaf
- @Bean
- public ShiroDialect getShiroDialect() {
- return new ShiroDialect();
- }
- html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org"
- xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
- <head>
- <meta charset="UTF-8">
- <title>首页title>
- head>
- <body>
-
- <div>
- <h1>首页h1>
- <p th:text="${msg}">p>
-
-
-
-
- <div shiro:notAuthenticated>
- <a th:href="@{/toLogin}">登录a>
- div>
-
- <hr>
-
- <div shiro:hasPermission="user:add">
- <a th:href="@{/user/add}">adda>
- div>
-
- <div shiro:hasPermission="user:update">
- <a th:href="@{/user/update}">updatea>
- div>
-
- div>
- body>
- html>
ShiroConfig
- @Configuration
- public class ShiroConfig {
-
- //3. shiroFilterFactoryBean
-
- @Bean
- public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
- ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
- // 设置安全管理器
- bean.setSecurityManager(defaultWebSecurityManager);
-
- //添加shiro的内置过滤器
- /*
- * anon: 无需认证就可以访问
- authc: 必须认证了才能访问
- user: 必须拥有记住我功能才能用
- perms: 拥有对某个资源的权限才能访问
- role: 拥有某个角色权限
- * */
-
- Map
filterMap = new LinkedHashMap<>(); - //授权,正常情况下,没有授权会跳转到为授权页面
- filterMap.put("/user/add","perms[user:add]");
- filterMap.put("/user/update","perms[user:update]");
- //拦截
- //filterMap.put("/user/add","authc");
- //filterMap.put("/user/update","authc");
- filterMap.put("/user/*","authc");
- bean.setFilterChainDefinitionMap(filterMap);
-
-
-
- //设置登录的请求
- bean.setLoginUrl("/toLogin");
- //未授权页面
- bean.setUnauthorizedUrl("/noauto");
-
- return bean;
- }
-
- //2. DefaultWebSecurityManager
-
- @Bean
- public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
-
- // 关联userRealm
- securityManager.setRealm(userRealm);
- return securityManager;
- }
- //1. 创建realm对象,需要自定义类
-
- @Bean
- public UserRealm userRealm() {
- return new UserRealm();
- }
-
- // 整合ShiroDialect: 用来整合 Shiro thymeleaf
- @Bean
- public ShiroDialect getShiroDialect() {
- return new ShiroDialect();
- }
-
- }
UserRealm
- //自定义的UserRealm
- public class UserRealm extends AuthorizingRealm {
- @Autowired
- UserService userService;
- //授权
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
- System.out.println("执行了=>授权doGetAuthorizationInfo");
-
- SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
-
- //拿到当前登录的这个对象
- Subject subject = SecurityUtils.getSubject();
- User currentUser = (User)subject.getPrincipal();//拿到user对象
-
- //设置当前用户的权限
- info.addStringPermission(currentUser.getPerms());
-
- return info;
- }
-
- //认证
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- System.out.println("执行了=>认证doGetAuthorizationInfo");
-
- UsernamePasswordToken userToken = (UsernamePasswordToken) token;
- // 虚拟用户
- //String name = "root";
- //String password = "123456";
- //if (!userToken.getUsername().equals(name)) {
- // return null;//抛出异常 UnknownAccountException
- //}
-
- // 真实数据库 用户名、密码, 数据中取
- User user = userService.queryUserByName(userToken.getUsername());
-
- if (user == null) {//没有这个人
- return null;
- }
-
- //配合首页使用,session实现
- //Subject currentSubject = SecurityUtils.getSubject();
- //Session session = currentSubject.getSession();
- //session.setAttribute("loginUser",user);
-
- // 密码认证,shiro做
- return new SimpleAuthenticationInfo(user,user.getPwd(),"");
- }
-
- }
MyController
- @Controller
- public class MyController {
-
- @RequestMapping({"/","/index"})
- public String toIndex(Model model) {
- model.addAttribute("msg","hello,Shiro");
- return "index";
- }
-
- @RequestMapping("/user/add")
- public String add() {
- return "user/add";
- }
-
- @RequestMapping("/user/update")
- public String update() {
- return "user/update";
- }
- @RequestMapping("/toLogin")
- public String toLogin() {
- return "login";
- }
- @RequestMapping("/login")
- public String login(String username, String password, Model model) {
- //获取一个用户
- Subject subject = SecurityUtils.getSubject();
- // 封装用户的登录数据
- UsernamePasswordToken token = new UsernamePasswordToken(username, password);
-
- try {
- subject.login(token);//执行登录的方法,如果没有异常就说明ok了
- return "index";
- } catch (UnknownAccountException e) {//用户名不存在
- model.addAttribute("msg","用户名错误");
- return "login";
- } catch (IncorrectCredentialsException e) {//密码不存在
- model.addAttribute("msg","密码错误");
- return "login";
- }
-
- }
- @RequestMapping("/noauto")
- @ResponseBody
- public String unauthorized() {
- return "未经授权,无法访问此页面";
- }
- }
pom依赖
- "1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0modelVersion>
- <parent>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-parentartifactId>
- <version>2.7.5version>
- <relativePath/>
- parent>
- <groupId>com.shuangmagroupId>
- <artifactId>shiro-springbootartifactId>
- <version>0.0.1-SNAPSHOTversion>
- <name>shiro-springbootname>
- <description>shiro-springbootdescription>
- <properties>
- <java.version>1.8java.version>
- properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-thymeleafartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-testartifactId>
- <scope>testscope>
- dependency>
-
- <dependency>
- <groupId>org.apache.shirogroupId>
- <artifactId>shiro-springartifactId>
- <version>1.10.0version>
- dependency>
-
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- dependency>
- <dependency>
- <groupId>log4jgroupId>
- <artifactId>log4jartifactId>
- <version>1.2.17version>
- dependency>
- <dependency>
- <groupId>com.alibabagroupId>
- <artifactId>druidartifactId>
- <version>1.2.11version>
- dependency>
- <dependency>
- <groupId>org.mybatis.spring.bootgroupId>
- <artifactId>mybatis-spring-boot-starterartifactId>
- <version>2.2.2version>
- dependency>
-
-
- <dependency>
- <groupId>com.github.theborakompanionigroupId>
- <artifactId>thymeleaf-extras-shiroartifactId>
- <version>2.1.0version>
- dependency>
- dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-maven-pluginartifactId>
- plugin>
- plugins>
- build>
-
- project>