day07【后台】SpringSecurity

1、权限控制框架

1.1、SpringSecurity 框架简介

用户登录系统时我们协助 SpringSecurity 把用户对应的角色、 权限组装好, 同时把各个资源所要求的权限信息设定好, 剩下的“ 登录验证”、 “ 权限验证” 等等工作都交给 SpringSecurity

1.2、权限控制相关概念

1.2.1、主体

英文单词: principal,使用系统的用户或设备或从其他系统远程登录的用户等等。 简单说就是谁使用系统,谁就是主体。

1.2.2、认证

英文单词: authentication,权限管理系统确认一个主体的身份, 允许主体进入系统。 简单说就是“主体” 证明自己是谁。

笼统的认为就是以前所做的登录操作。

1.2.3、授权

英文单词: authorization,将操作系统的“权力”“授予”“主体”, 这样主体就具备了操作系统中特定功能的能力。

所以简单来说, 授权就是给用户分配权限

2、搭建SpringMVC环境

2.1、创建Maven工程

  • 新建一个Maven工程

  • 打包方式为war

  • 生成web.xml文件

2.2、加入SpringMVC所需依赖

  • 在工程的pom文件下添加所需依赖
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.atguigu.security</groupId><artifactId>SecurityLearn</artifactId><version>0.0.1-SNAPSHOT</version><packaging>war</packaging><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.3.20.RELEASE</version></dependency><!-- 引入Servlet容器中相关依赖 --><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope></dependency><!-- JSP页面使用的依赖 --><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.1.3-b06</version><scope>provided</scope></dependency></dependencies></project>

2.3、SpringMVC配置文件

  • resources文件夹下添加SpringMVC的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"><context:component-scanbase-package="com.atguigu.security"></context:component-scan><beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/views/"></property><property name="suffix" value=".jsp"></property></bean><mvc:annotation-driven></mvc:annotation-driven><mvc:default-servlet-handler /></beans>

2.4、配置DispatcherServlet

  • web.xml文件中配置DispatcherServlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"version="2.5"><!-- The front controller of this Spring Web application, responsible for handling all application requests --><servlet><servlet-name>springDispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><!-- Map all requests to the DispatcherServlet for handling --><servlet-mapping><servlet-name>springDispatcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>

2.5、添加Controller组件

  • 在工程下添加如下控制器

  • AdminController.java代码
@Controller
public class AdminController {@GetMapping("/main.html")public String main(){return "main";}@RequestMapping("/to/no/auth/page.html")public String toNoAuthPage() {return "no_auth";}}
  • GongfuController.java代码
@Controller
public class GongfuController {@GetMapping("/level1/{path}")public String leve1Page(@PathVariable("path")String path){return "/level1/"+path;}@GetMapping("/level2/{path}")public String leve2Page(@PathVariable("path")String path){return "/level2/"+path;}@GetMapping("/level3/{path}")public String leve3Page(@PathVariable("path")String path){return "/level3/"+path;}}

2.6、加入前端页面资源

  • 加入如下前端资源

2.7、测试环境

  • 测试成功哦~

3、加入SpringSecurity环境

3.1、引入依赖

  • pom文件下添加如下依赖,引入SpringSecurity所需的jar
<!-- SpringSecurity对Web应用进行权限管理 -->
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId><version>4.2.10.RELEASE</version>
</dependency><!-- SpringSecurity配置 -->
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId><version>4.2.10.RELEASE</version>
</dependency><!-- SpringSecurity标签库 -->
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-taglibs</artifactId><version>4.2.10.RELEASE</version>
</dependency>

3.2、权限控制之Filter

  • web.xml中添加用于权限认证的Filter

    • SpringSecurity使用的是过滤器Filter而不是拦截器Interceptor, 意味着SpringSecurity能够管理的不仅仅是 SpringMVC 中的 handler 请求, 还包含Web应用中所有请求,比如:
      项目中的静态资源也会被拦截, 从而进行权限控制
    • 标签中必须是springSecurityFilterChain。 因为springSecurityFilterChainIOC 容器中对应真正执行权限控制的二十几个 Filter, 只有叫这个名字才能够加载到这些 Filter
<filter><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping><filter-name>springSecurityFilterChain</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

3.3、加入配置类

  • 在工程下添加关于SpringSecurity配置类,注意!这个类一定要放在自动扫描的包下,否则所有配置都不会生效!

// 注意!这个类一定要放在自动扫描的包下,否则所有配置都不会生效!// 将当前类标记为配置类
@Configuration// 启用Web环境下权限控制功能
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {}

3.4、拦截效果

  • 访问任何页面,都会跳转回SpringSecurity自带的登录页面

4、实验一:放行首页和静态资源

4.1、重写configure方法

  • 父类中的configure方法代码如下
/*** Override this method to configure the {@link HttpSecurity}. Typically subclasses* should not invoke this method by calling super as it may override their* configuration. The default configuration is:** <pre>* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();* </pre>** @param http the {@link HttpSecurity} to modify* @throws Exception if an error occurs*/
// @formatter:off
protected void configure(HttpSecurity http) throws Exception {logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
}
  • 重写WebSecurityConfigurerAdapter父类中的configure方法
// 注意!这个类一定要放在自动扫描的包下,否则所有配置都不会生效!// 将当前类标记为配置类
@Configuration
// 启用Web环境下权限控制功能
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                        // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")         // 针对/index.jsp路径进行授权.permitAll()                             // 可以无条件访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问;}}

4.2、先后顺序

4.2.1、注意顺序

  • 设置授权信息时需要注意, 范围小的放在前面、 范围大的放在后面。 不然的话, 小范围的设置会被大范围设置覆盖。

4.2.2、来个死循环

  • 这是之后复习时补的笔记,所以需要用到后面的知识哟~我们将范围大的授权信息放在前面
@Override
protected void configure(HttpSecurity security) throws Exception {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);       // 设置数据源tokenRepository.setCreateTableOnStartup(true);  // 标记为true,才会创建数据库表tokenRepository.initDao();                        // 执行建表SQLsecurity.authorizeRequests()                      // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().authorizeRequests()                        // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.antMatchers("/level1/**")                  // 针对/level1/**路径设置访问要求.hasRole("学徒")                             // 要求用户具备“学徒”角色才可以访问.antMatchers("/level2/**")                    // 针对/level2/**路径设置访问要求.hasAuthority("内门弟子")                      // 要求用户具备“内门弟子”权限才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址.usernameParameter("loginAcct")             // 定制登录账号的请求参数名.passwordParameter("userPswd")             // 定制登录密码的请求参数名.defaultSuccessUrl("/main.html")           // 登录成功后前往的地址.and().logout()                                    // 开启退出功能.logoutUrl("/do/logout.html")                // 指定处理退出请求的URL地址.logoutSuccessUrl("/index.jsp")              // 退出成功后前往的地址.and().exceptionHandling()                             // 指定异常处理器//.accessDeniedPage("/to/no/auth/page.html")        // 访问被拒绝时前往的页面.accessDeniedHandler(new AccessDeniedHandler() {  // 自定义异常处理逻辑@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response,AccessDeniedException accessDeniedException) throws IOException, ServletException {request.setAttribute("message", "抱歉!您无法访问这个资源!☆☆☆");request.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(request, response);}}).and().rememberMe()                               // 开启记住我功能.tokenRepository(tokenRepository)         // 启用令牌仓库功能;}

4.2.3、重定向次数过多

  • 分析:

    • 首先,想要访问任意请求,则必须活已授权(登录),所以就会跳转到index.jsp页面进行登录
    • 然后嘞?浏览器就会跳转至index.jsp页面,前提嘞?访问任意请求的前提还是必须已授权(登录)
    • 然后嘞?浏览器就会跳转至index.jsp页面,前提嘞?访问任意请求的前提还是必须已授权(登录)
    • 浏览器:我特么可能不是人,但你是真的狗~
security.authorizeRequests()                     // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().authorizeRequests()                        // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.antMatchers("/level1/**")                  // 针对/level1/**路径设置访问要求.hasRole("学徒")                             // 要求用户具备“学徒”角色才可以访问.antMatchers("/level2/**")                    // 针对/level2/**路径设置访问要求.hasAuthority("内门弟子")                      // 要求用户具备“内门弟子”权限才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址

4.3、实验效果

  • 可以正常访问index.jsp页面和静态资源(因为index.jsp页面需要用到静态资源,所以能正常访问index.jsp页面,就说明静态资源也能正常访问)

  • 其他资源就不行咯~

5、实验二:未认证跳转至登录页面

5.1、指定登录页面地址

  • configure方法中指定登录页面地址,以及登录表单提交的地址
@Override
protected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                      // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址;}

5.2、实验效果

  • 访问没有授权的资源,则会自动跳转回index.jsp页面

6、实验三:设置登录系统的账号、 密码

6.1、思路

6.2、页面设置

  • 修改页面表单的提交地址

  • CSRF

  • 表单中用户名输入框和密码输入框的name属性要和之后的配置一致

  • 表单代码如下:
<p>${SPRING_SECURITY_LAST_EXCEPTION.message}</p>
<form action="${pageContext.request.contextPath }/do/login.html" method="post"><input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/><div class="layadmin-user-login-main"><div class="layadmin-user-login-box layadmin-user-login-header"><h2>layuiAdmin</h2><p>layui 官方出品的单页面后台管理模板系统</p></div><divclass="layadmin-user-login-box layadmin-user-login-body layui-form">  <div class="layui-form-item">                      <labelclass="layadmin-user-login-icon layui-icon layui-icon-username"for="LAY-user-login-username"></label>       <!-- input的name属性值必须符合SpringSecurity规则,除非专门进行了定制,否则用户名必须使用username,密码必须使用password --><input type="text"name="loginAcct" id="LAY-user-login-username" lay-verify="required"placeholder="用户名" class="layui-input"></div><div class="layui-form-item"><labelclass="layadmin-user-login-icon layui-icon layui-icon-password"for="LAY-user-login-password"></label> <input type="text"name="userPswd" id="LAY-user-login-password" lay-verify="required"placeholder="密码" class="layui-input"></div><div class="layui-form-item"><div class="layui-row"><div class="layui-col-xs7"><labelclass="layadmin-user-login-icon layui-icon layui-icon-vercode"for="LAY-user-login-vercode"></label><input type="text"name="vercode" id="LAY-user-login-vercode" lay-verify="required"placeholder="图形验证码" class="layui-input"></div><div class="layui-col-xs5"><div style="margin-left: 10px;"><img src="https://www.oschina.net/action/user/captcha"class="layadmin-user-login-codeimg" id="LAY-user-get-vercode"></div></div></div></div><div class="layui-form-item" style="margin-bottom: 20px;"><input type="checkbox" name="remember-me" lay-skin="primary"title="记住我"> <a href="forget.html"class="layadmin-user-jump-change layadmin-link"style="margin-top: 7px;">忘记密码?</a></div><div class="layui-form-item"><button type="submit" class="layui-btn layui-btn-fluid" lay-submitlay-filter="LAY-user-login-submit">登 入</button></div><div class="layui-trans layui-form-item layadmin-user-login-other"><label>社交账号登入</label> <a href="javascript:;"><iclass="layui-icon layui-icon-login-qq"></i></a> <a href="javascript:;"><iclass="layui-icon layui-icon-login-wechat"></i></a> <ahref="javascript:;"><iclass="layui-icon layui-icon-login-weibo"></i></a> <a href="reg.html"class="layadmin-user-jump-change layadmin-link">注册帐号</a></div></div></div>
</form>

6.3、重写configure方法

  • protected void configure(AuthenticationManagerBuilder builder) throws Exception {方法;登录页面中,表单提交的用户名字段名必须是loginAcct,表单提交的密码字段名必须是userPswd
@Override
protected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                      // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址.usernameParameter("loginAcct")             // 定制登录账号的请求参数名.passwordParameter("userPswd")             // 定制登录密码的请求参数名.defaultSuccessUrl("/main.html")           // 登录成功后前往的地址;}
  • protected void configure(HttpSecurity security) throws Exception {方法

    • 分配Admin角色给tom,密码为123123
    • 分配UPDATE权限给jerry,密码为123123
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {builder.inMemoryAuthentication()   // 在内存中完成账号、密码的检查.withUser("tom")         // 指定账号.password("123123")            // 指定密码.roles("ADMIN")                // 指定当前用户的角色.and().withUser("jerry")          // 指定账号.password("123123")            // 指定密码.authorities("UPDATE")     // 指定当前用户的权限;}

6.4、CSRF介绍

  • 了解跨站请求伪造:Cross-site request forgery 跨站请求伪造,发送登录请求时没有携带_csrf 值, 则返回下面错误:

  • 从钓鱼网站的页面提交的请求无法携带正确、 被承认的令牌

6.5、实验效果

  • 带上CSRF,则可以成功登陆

  • 删除CSRF,则无法登陆

7、实验四:用户注销

7.1、禁用CSRF版本

7.1.1、重写configure方法

  • 重写configure方法

    • 禁用CSRF功能
    • 开启注销功能
@Override
protected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                      // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址.usernameParameter("loginAcct")             // 定制登录账号的请求参数名.passwordParameter("userPswd")             // 定制登录密码的请求参数名.defaultSuccessUrl("/main.html")           // 登录成功后前往的地址.and().csrf().disable()                                    // 禁用CSRF功能.logout()                                    // 开启退出功能.logoutUrl("/do/logout.html")                // 指定处理退出请求的URL地址.logoutSuccessUrl("/index.jsp")              // 退出成功后前往的地址;}

7.1.2、提交注销请求

  • 在上方navbar中,指定注销请求的地址

<!-- 禁用CSRF功能的前提下,最简单的退出操作 -->
<a href="${pageContext.request.contextPath }/do/logout.html">退出</a>

7.2、启用CSRF版本

7.2.1、重写configure方法

  • 重写configure方法

    • 启用CSRF功能
    • 开启注销功能
@Override
protected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                      // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址.usernameParameter("loginAcct")             // 定制登录账号的请求参数名.passwordParameter("userPswd")             // 定制登录密码的请求参数名.defaultSuccessUrl("/main.html")           // 登录成功后前往的地址
//      .and()
//      .csrf()
//      .disable()                                  // 禁用CSRF功能.and().logout()                                  // 开启退出功能.logoutUrl("/do/logout.html")                // 指定处理退出请求的URL地址.logoutSuccessUrl("/index.jsp")              // 退出成功后前往的地址;}

7.2.2、提交注销请求

  • navbar.jsp页面中,提交注销请求的同时,附带上CSRF的值

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<div class="layui-header"><div class="layui-logo" onclick="location.href='${PATH }/main.html'">武林秘籍管理系统</div><!-- 头部区域(可配合layui已有的水平导航) --><ul class="layui-nav layui-layout-left"><li class="layui-nav-item"><a href="">控制台</a></li><li class="layui-nav-item"><a href="">商品管理</a></li><li class="layui-nav-item"><a href="">用户</a></li><li class="layui-nav-item"><a href="javascript:;">其它系统</a><dl class="layui-nav-child"><dd><a href="">邮件管理</a></dd><dd><a href="">消息管理</a></dd><dd><a href="">授权管理</a></dd></dl></li></ul><ul class="layui-nav layui-layout-right"><li class="layui-nav-item"><a href="javascript:;"> <imgsrc="http://t.cn/RCzsdCq" class="layui-nav-img"> 张无忌</a><dl class="layui-nav-child"><dd><a href="">基本资料</a></dd><dd><a href="">安全设置</a></dd></dl></li><li class="layui-nav-item"><!-- 禁用CSRF功能的前提下,最简单的退出操作 --><%-- <a href="${pageContext.request.contextPath }/do/logout.html">退出</a> --%><form id="logoutForm" action="${pageContext.request.contextPath }/do/logout.html" method="post"><input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/></form><a id="logoutAnchor" href="">退出</a><script type="text/javascript">window.onload = function() {// 给超链接的DOM对象绑定单击响应函数document.getElementById("logoutAnchor").onclick = function() {// 提交包含csrf参数的表单document.getElementById("logoutForm").submit();// 取消超链接的默认行为return false;};};</script></li></ul>
</div>

7.3、实验效果

8、实验五:基于角色或权限访问

8.1、重写configure方法

  • 设置角色或权限与资源的关联关系
@Override
protected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                      // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.antMatchers("/level1/**")                  // 针对/level1/**路径设置访问要求.hasRole("学徒")                             // 要求用户具备“学徒”角色才可以访问.antMatchers("/level2/**")                    // 针对/level2/**路径设置访问要求.hasAuthority("内门弟子")                      // 要求用户具备“内门弟子”权限才可以访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址.usernameParameter("loginAcct")             // 定制登录账号的请求参数名.passwordParameter("userPswd")             // 定制登录密码的请求参数名.defaultSuccessUrl("/main.html")           // 登录成功后前往的地址.and().logout()                                    // 开启退出功能.logoutUrl("/do/logout.html")                // 指定处理退出请求的URL地址.logoutSuccessUrl("/index.jsp")              // 退出成功后前往的地址;}
  • 设置用户与角色或权限的关联关系
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {builder.inMemoryAuthentication()           // 在内存中完成账号、密码的检查.withUser("tom")                 // 指定账号.password("123123")                    // 指定密码.roles("ADMIN","学徒")             // 指定当前用户的角色.and().withUser("jerry")                  // 指定账号.password("123123")                    // 指定密码.authorities("UPDATE","内门弟子")        // 指定当前用户的权限;}

8.2、实验结果

8.3、源码分析

8.3.1、添加角色

  • Debug运行至如下代码处

  • Step into进入.hasRole("学徒") 方法
public ExpressionInterceptUrlRegistry hasRole(String role) {return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}
  • Step into进入ExpressionUrlAuthorizationConfigurer.hasRole(role)方法
private static String hasRole(String role) {Assert.notNull(role, "role cannot be null");if (role.startsWith("ROLE_")) {throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '"+ role + "'");}return "hasRole('ROLE_" + role + "')";
}
  • 结论:添加角色时,SpringSecurity会在角色名称前面添加ROLE_字符串,结论:我们自定义的角色名称不能以ROLE_开头(it is automatically added),不然就会抛异常

8.3.2、分配角色

  • Debug至如下代码处

  • Step into进入.roles("ADMIN","学徒")方法
public UserDetailsBuilder roles(String... roles) {this.user.roles(roles);return this;
}
  • Step into进入this.user.roles(roles)方法
public UserBuilder roles(String... roles) {List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(roles.length);for (String role : roles) {Assert.isTrue(!role.startsWith("ROLE_"), role+ " cannot start with ROLE_ (it is automatically added)");authorities.add(new SimpleGrantedAuthority("ROLE_" + role));}return authorities(authorities);
}
  • Step into进入authorities(authorities)方法
public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {this.authorities = new ArrayList<GrantedAuthority>(authorities);return this;
}

8.3.3、添加权限

  • Debug至如下代码处

  • Step into进入.hasAuthority("内门弟子")方法
public ExpressionInterceptUrlRegistry hasAuthority(String authority) {return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
}
  • Step into进入ExpressionUrlAuthorizationConfigurer.hasAuthority(authority)方法
private static String hasAuthority(String authority) {return "hasAuthority('" + authority + "')";
}
  • 结论:不像添加角色那样,添加权限时,SpringSecurity并没有为权限添加前缀

8.3.4、分配权限

  • Debug至如下代码处

  • Step into进入.authorities("UPDATE","内门弟子")方法
public UserDetailsBuilder authorities(String... authorities) {this.user.authorities(authorities);return this;
}
  • Step into进入this.user.authorities(authorities)方法
public UserBuilder authorities(String... authorities) {return authorities(AuthorityUtils.createAuthorityList(authorities));
}
  • Step into进入AuthorityUtils.createAuthorityList(authorities)方法
public static List<GrantedAuthority> createAuthorityList(String... roles) {List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(roles.length);for (String role : roles) {authorities.add(new SimpleGrantedAuthority(role));}return authorities;
}
  • Step into进入authorities(authorities)方法
public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {this.authorities = new ArrayList<GrantedAuthority>(authorities);return this;
}

8.4、“ROLE_”的坑

  • 之所以要强调这个事情, 是因为将来从数据库查询得到的用户信息、 角色信息、 权限信息需要我们自己手动组装。 手动组装时需要我们自己给角色字符串前面加“ROLE_”前缀。

9、实验六:自定义 403 错误页面

9.1、添加403错误页面

  • 403错误页面:no_auth.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<%pageContext.setAttribute("PATH", request.getContextPath());
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport"content="width=device-width, initial-scale=1, maximum-scale=1">
<title>武林秘籍管理系统</title>
<link rel="stylesheet" href="${PATH }/layui/css/layui.css">
</head>
<body class="layui-layout-body"><div class="layui-layout layui-layout-admin"><!-- 顶部导航 --><%@include file="/WEB-INF/include/navbar.jsp" %><!-- 侧边栏 --><%@include file="/WEB-INF/include/sidebar.jsp" %><div class="layui-body"><!-- 内容主体区域 --><div style="padding: 15px;"><h1>非常抱歉!您没有访问这个功能的权限!(回家照照镜子)</h1><h2>${message }</h2></div></div><div class="layui-footer"></div></div><script src="${PATH }/layui/layui.js"></script><script>//JavaScript代码区域layui.use('element', function() {var element = layui.element;});</script>
</body>
</html>

9.2、来到403错误页面

  • 重写configure方法:指定访问被拒绝时,跳转的页面
@Override
protected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                      // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.antMatchers("/level1/**")                  // 针对/level1/**路径设置访问要求.hasRole("学徒")                             // 要求用户具备“学徒”角色才可以访问.antMatchers("/level2/**")                    // 针对/level2/**路径设置访问要求.hasAuthority("内门弟子")                      // 要求用户具备“内门弟子”权限才可以访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址.usernameParameter("loginAcct")             // 定制登录账号的请求参数名.passwordParameter("userPswd")             // 定制登录密码的请求参数名.defaultSuccessUrl("/main.html")           // 登录成功后前往的地址.and().logout()                                    // 开启退出功能.logoutUrl("/do/logout.html")                // 指定处理退出请求的URL地址.logoutSuccessUrl("/index.jsp")              // 退出成功后前往的地址.and().exceptionHandling()                     // 指定异常处理器.accessDeniedPage("/to/no/auth/page.html")  // 访问被拒绝时前往的页面;}
  • 实验效果

9.3、携带异常信息

  • 重写configure方法:访问被拒绝时,设置异常信息,并转发至指定页面
@Override
protected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                      // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.antMatchers("/level1/**")                  // 针对/level1/**路径设置访问要求.hasRole("学徒")                             // 要求用户具备“学徒”角色才可以访问.antMatchers("/level2/**")                    // 针对/level2/**路径设置访问要求.hasAuthority("内门弟子")                      // 要求用户具备“内门弟子”权限才可以访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址.usernameParameter("loginAcct")             // 定制登录账号的请求参数名.passwordParameter("userPswd")             // 定制登录密码的请求参数名.defaultSuccessUrl("/main.html")           // 登录成功后前往的地址.and().logout()                                    // 开启退出功能.logoutUrl("/do/logout.html")                // 指定处理退出请求的URL地址.logoutSuccessUrl("/index.jsp")              // 退出成功后前往的地址.and().exceptionHandling()                             // 指定异常处理器//.accessDeniedPage("/to/no/auth/page.html")        // 访问被拒绝时前往的页面.accessDeniedHandler(new AccessDeniedHandler() {  // 自定义异常处理逻辑@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response,AccessDeniedException accessDeniedException) throws IOException, ServletException {request.setAttribute("message", "抱歉!您无法访问这个资源!☆☆☆");request.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(request, response);}});}
  • 实验效果

10、实验七:记住我-内存版(不重要)

10.1、开启记住我功能

  • configure方法中开启记住我功能
@Override
protected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                      // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.antMatchers("/level1/**")                  // 针对/level1/**路径设置访问要求.hasRole("学徒")                             // 要求用户具备“学徒”角色才可以访问.antMatchers("/level2/**")                    // 针对/level2/**路径设置访问要求.hasAuthority("内门弟子")                      // 要求用户具备“内门弟子”权限才可以访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址.usernameParameter("loginAcct")             // 定制登录账号的请求参数名.passwordParameter("userPswd")             // 定制登录密码的请求参数名.defaultSuccessUrl("/main.html")           // 登录成功后前往的地址.and().logout()                                    // 开启退出功能.logoutUrl("/do/logout.html")                // 指定处理退出请求的URL地址.logoutSuccessUrl("/index.jsp")              // 退出成功后前往的地址.and().exceptionHandling()                             // 指定异常处理器//.accessDeniedPage("/to/no/auth/page.html")        // 访问被拒绝时前往的页面.accessDeniedHandler(new AccessDeniedHandler() {  // 自定义异常处理逻辑@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response,AccessDeniedException accessDeniedException) throws IOException, ServletException {request.setAttribute("message", "抱歉!您无法访问这个资源!☆☆☆");request.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(request, response);}}).and().rememberMe()                               // 开启记住我功能;}
  • 注意:如果不能使用“remember-me”作为请求参数名称,可以使用rememberMeParameter()方法定制

10.2、页面CheckBox

  • 页面中CheckBoxname属性的值一定要设置为"remember-me"`
<div class="layui-form-item" style="margin-bottom: 20px;"><input type="checkbox" name="remember-me" lay-skin="primary"title="记住我"> <a href="forget.html"class="layadmin-user-jump-change layadmin-link"style="margin-top: 7px;">忘记密码?</a>
</div>

10.3、记住我原理

  • 未登录之前只有JSESSIONID

  • 点击记住我,登陆之后,多了一个name=remember-mecookie,并且过期时间为7.1,这说明我们关闭浏览器再打开页面,也能直接访问武林秘籍

11、实验八:记住我-数据库版(不重要)

11.1、加入依赖

  • 在工程的pom文件中添加数据库所需的依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.12</version>
</dependency><!-- mysql驱动 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version>
</dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>4.3.20.RELEASE</version>
</dependency>

11.2、配置数据源

  • SpringMVC配置文件中配置数据源,以及将数据源交由JdbcTemplate管理
<!-- 配置数据源 -->
<bean id="dataSource"class="com.alibaba.druid.pool.DruidDataSource"><property name="username" value="root"></property><property name="password" value="root"></property><property name="url"value="jdbc:mysql://localhost:3306/security?useSSL=false"></property><property name="driverClassName"value="com.mysql.jdbc.Driver"></property>
</bean><!-- jdbcTemplate -->
<bean id="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property>
</bean>

11.3、数据库表

  • 创建数据库
CREATE DATABASE `security` CHARACTER SET utf8;
  • 创建数据库表(如果修改了源码,则这步可省略不做)
CREATE TABLE persistent_logins (username VARCHAR (64) NOT NULL,series VARCHAR (64) PRIMARY KEY,token VARCHAR (64) NOT NULL,last_used TIMESTAMP NOT NULL
);

11.4、如何修改源码

  • 包名、类名均需与目标源码相同,然后将目标源码拷贝过来,做修改即可

/** Copyright 2002-2012 the original author or authors.** Licensed 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.*/
package org.springframework.security.web.authentication.rememberme;import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;/*** JDBC based persistent login token repository implementation.** @author Luke Taylor* @since 2.0*/
public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implementsPersistentTokenRepository {// ~ Static fields/initializers// =====================================================================================/** Default SQL for creating the database table to store the tokens */public static final String CREATE_TABLE_SQL = "create table if not exists persistent_logins (username varchar(64) not null, series varchar(64) primary key, "+ "token varchar(64) not null, last_used timestamp not null)";/** The default SQL used by the <tt>getTokenBySeries</tt> query */public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";/** The default SQL used by <tt>createNewToken</tt> */public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";/** The default SQL used by <tt>updateToken</tt> */public static final String DEF_UPDATE_TOKEN_SQL = "update persistent_logins set token = ?, last_used = ? where series = ?";/** The default SQL used by <tt>removeUserTokens</tt> */public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?";// ~ Instance fields// ================================================================================================private String tokensBySeriesSql = DEF_TOKEN_BY_SERIES_SQL;private String insertTokenSql = DEF_INSERT_TOKEN_SQL;private String updateTokenSql = DEF_UPDATE_TOKEN_SQL;private String removeUserTokensSql = DEF_REMOVE_USER_TOKENS_SQL;private boolean createTableOnStartup;public void initDao() {if (createTableOnStartup) {getJdbcTemplate().execute(CREATE_TABLE_SQL);}}public void createNewToken(PersistentRememberMeToken token) {getJdbcTemplate().update(insertTokenSql, token.getUsername(), token.getSeries(),token.getTokenValue(), token.getDate());}public void updateToken(String series, String tokenValue, Date lastUsed) {getJdbcTemplate().update(updateTokenSql, tokenValue, lastUsed, series);}/*** Loads the token data for the supplied series identifier.** If an error occurs, it will be reported and null will be returned (since the result* should just be a failed persistent login).** @param seriesId* @return the token matching the series, or null if no match found or an exception* occurred.*/public PersistentRememberMeToken getTokenForSeries(String seriesId) {try {return getJdbcTemplate().queryForObject(tokensBySeriesSql,new RowMapper<PersistentRememberMeToken>() {public PersistentRememberMeToken mapRow(ResultSet rs, int rowNum)throws SQLException {return new PersistentRememberMeToken(rs.getString(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4));}}, seriesId);}catch (EmptyResultDataAccessException zeroResults) {if (logger.isDebugEnabled()) {logger.debug("Querying token for series '" + seriesId+ "' returned no results.", zeroResults);}}catch (IncorrectResultSizeDataAccessException moreThanOne) {logger.error("Querying token for series '" + seriesId+ "' returned more than one value. Series" + " should be unique");}catch (DataAccessException e) {logger.error("Failed to load token for series " + seriesId, e);}return null;}public void removeUserTokens(String username) {getJdbcTemplate().update(removeUserTokensSql, username);}/*** Intended for convenience in debugging. Will create the persistent_tokens database* table when the class is initialized during the initDao method.** @param createTableOnStartup set to true to execute the*/public void setCreateTableOnStartup(boolean createTableOnStartup) {this.createTableOnStartup = createTableOnStartup;}
}
  • 重要代码:创建数据库表
/** Default SQL for creating the database table to store the tokens */
public static final String CREATE_TABLE_SQL = "create table if not exists persistent_logins (username varchar(64) not null, series varchar(64) primary key, "+ "token varchar(64) not null, last_used timestamp not null)";private boolean createTableOnStartup;public void initDao() {if (createTableOnStartup) {getJdbcTemplate().execute(CREATE_TABLE_SQL);}
}
  • 为什么会加载我们的建立的类?类加载器的就近原则

11.5、配置Token

  • 重写configure方法

    • 注入数据源
    • 创建数据库表
    • 启用令牌仓库功能
@Override
protected void configure(HttpSecurity security) throws Exception {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);       // 设置数据源tokenRepository.setCreateTableOnStartup(true);  // 标记为true,才会创建数据库表tokenRepository.initDao();                        // 执行建表SQLsecurity.authorizeRequests()                      // 对请求进行授权.antMatchers("/index.jsp", "/layui/**")       // 针对/index.jsp路径进行授权.permitAll()                               // 可以无条件访问.antMatchers("/level1/**")                  // 针对/level1/**路径设置访问要求.hasRole("学徒")                             // 要求用户具备“学徒”角色才可以访问.antMatchers("/level2/**")                    // 针对/level2/**路径设置访问要求.hasAuthority("内门弟子")                      // 要求用户具备“内门弟子”权限才可以访问.and().authorizeRequests()                        // 对请求进行授权.anyRequest()                             // 任意请求.authenticated()                         // 需要登录以后才可以访问.and().formLogin()                                // 使用表单形式登录// 关于loginPage()方法的特殊说明// 指定登录页的同时会影响到:“提交登录表单的地址”、“退出登录地址”、“登录失败地址”// /index.jsp GET - the login form 去登录页面// /index.jsp POST - process the credentials and if valid authenticate the user 提交登录表单// /index.jsp?error GET - redirect here for failed authentication attempts 登录失败// /index.jsp?logout GET - redirect here after successfully logging out 退出登录.loginPage("/index.jsp")                   // 指定登录页面(如果没有指定会访问SpringSecurity自带的登录页)// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST.loginProcessingUrl("/do/login.html")        // 指定提交登录表单的地址.usernameParameter("loginAcct")             // 定制登录账号的请求参数名.passwordParameter("userPswd")             // 定制登录密码的请求参数名.defaultSuccessUrl("/main.html")           // 登录成功后前往的地址.and().logout()                                    // 开启退出功能.logoutUrl("/do/logout.html")                // 指定处理退出请求的URL地址.logoutSuccessUrl("/index.jsp")              // 退出成功后前往的地址.and().exceptionHandling()                             // 指定异常处理器//.accessDeniedPage("/to/no/auth/page.html")        // 访问被拒绝时前往的页面.accessDeniedHandler(new AccessDeniedHandler() {  // 自定义异常处理逻辑@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response,AccessDeniedException accessDeniedException) throws IOException, ServletException {request.setAttribute("message", "抱歉!您无法访问这个资源!☆☆☆");request.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(request, response);}}).and().rememberMe()                               // 开启记住我功能.tokenRepository(tokenRepository)         // 启用令牌仓库功能;}

11.6、实验效果

  • 启动web应用程序,则会创建数据库表

  • 用户登录之后,数据库表中便会插入一条Token

  • 用户注销之后,相应的Token便会被删除

11.7、修改源码建议

  • 尽量不要修改,因为会打乱原本框架的代码执行逻辑

12、实验九:查询数据库完成认证

12.1、创建数据库表

  • 创建t_admin
use security;
drop table if exists t_admin;
create table t_admin
(id int not null auto_increment,        # 主键loginacct varchar(255) not null,    # 登录账号userpswd char(32) not null,           # 登录密码username varchar(255) not null,       # 昵称email varchar(255) not null,            # 邮件地址createtime char(19),              # 创建时间primary key (id)                      # 逐渐
);
  • 插入测试数据

12.2、创建实体类

  • 创建Admin实体类

public class Admin {private Integer id;private String loginacct;private String userpswd;private String username;private String email;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getLoginacct() {return loginacct;}public void setLoginacct(String loginacct) {this.loginacct = loginacct;}public String getUserpswd() {return userpswd;}public void setUserpswd(String userpswd) {this.userpswd = userpswd;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}}

12.3、自定义数据库查询方式

12.3.1、UserDetailsService接口

  • UserDetailsService接口定义的规范:根据用户名查询用户信息,并为其分配角色或权限
public interface UserDetailsService {// ~ Methods// ========================================================================================================/*** Locates the user based on the username. In the actual implementation, the search* may possibly be case sensitive, or case insensitive depending on how the* implementation instance is configured. In this case, the <code>UserDetails</code>* object that comes back may have a username that is of a different case than what* was actually requested..** @param username the username identifying the user whose data is required.** @return a fully populated user record (never <code>null</code>)** @throws UsernameNotFoundException if the user could not be found or the user has no* GrantedAuthority*/UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

12.3.2、创建UserDetailsService类

  • UserDetailsService类的作用:

    • 根据表单提交的数据查询Admin对象
    • 设置Admin对象的角色或权限信息
    • Admin对象的信息封装置UserDetails中(User类实现了UserDetails接口)
    • 注意:如果要分配角色,需要以ROLE_开头哦

@Component
public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate JdbcTemplate jdbcTemplate;// 总目标:根据表单提交的用户名查询User对象,并装配角色、权限等信息@Overridepublic UserDetails loadUserByUsername(// 表单提交的用户名String username ) throws UsernameNotFoundException {// 1.从数据库查询Admin对象String sql = "SELECT id,loginacct,userpswd,username,email FROM t_admin WHERE loginacct=?";List<Admin> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Admin.class), username);Admin admin = list.get(0);// 2.给Admin设置角色权限信息List<GrantedAuthority> authorities = new ArrayList<>();authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));authorities.add(new SimpleGrantedAuthority("UPDATE"));// 3.把admin对象和authorities封装到UserDetails中String userpswd = admin.getUserpswd();return new User(username, userpswd, authorities);}}

12.4、ROLE_ 前缀问题

在自定义的 UserDetailsService 中创建权限列表时,使用org.springframework.security.core.authority.AuthorityUtils.createAuthorityList(String...)工具方法获取创建 SimpleGrantedAuthority 对象添加角色时需要手动在角色名称前加ROLE_前缀

12.5、重写configure方法

  • 在配置类中启用我们自定义的数据库查询方式

@Autowired
private MyUserDetailsService myUserDetailsService;@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {/*builder.inMemoryAuthentication()         // 在内存中完成账号、密码的检查.withUser("tom")                 // 指定账号.password("123123")                    // 指定密码.roles("ADMIN","学徒")             // 指定当前用户的角色.and().withUser("jerry")                  // 指定账号.password("123123")                    // 指定密码.authorities("UPDATE","内门弟子")        // 指定当前用户的权限;*/// 装配userDetailsService对象builder.userDetailsService(userDetailsService);}

12.6、实验效果

  • 同样OK

day07【后台】SpringSecurity相关推荐

  1. Day07 后台管理与发布文章

    Day07 后台管理与发布文章 源代码: https://github.com/LToddy/blog 技术交流群:630398887(欢迎一起吹牛) 写在前面的话:如果你实在不会写页面,复制粘贴你会 ...

  2. 木字楠后台管理系统开发(4):SpringSecurity引入并编写登陆接口

  3. echarts前后端交互数据_SpringBoot2.0实战(26)整合SpringSecurity前后端分离JSON交互...

    在前端的文章中,我们实现了使用 SpringSecurity 实现登录鉴权,并使用数据库存储用户信息,实现登录鉴权 SpringBoot2.0实战(24)整合SpringSecurity之最简登录方法 ...

  4. 若依管理系统——前后端分离版(二)登陆接口分析及SpringSecurity的登陆认证流程

    一.登陆流程分析 0. 流程整理 1. 图片验证码接口/captchaImage 2.登陆验证接口/login 2.1 校验图片验证码 2.1 查询用户信息 2.3查询用户的权限信息 2.4 生成令牌 ...

  5. RuoYi后台系统权限管理解析

    一.前言 最近在学习spring security,自己也了些小的demo.也看了几个优秀的后台管理的开源项目.今天聊一下若依系统的权限管理的详细流程. 二.权限管理模型 若依使用的也是当前最流行的R ...

  6. Vue 动态路由的实现以及 Springsecurity 按钮级别的权限控制

    思路: 动态路由实现:在导航守卫中判断用户是否有用户信息,通过调用接口,拿到后台根据用户角色生成的菜单树,格式化菜单树结构信息并递归生成层级路由表并使用Vuex保存,通过 router.addRout ...

  7. SpringSecurity - 基础篇

    文章目录 一.SpringSecurity能做什么 二.SpringSecurity替代方案 三.权限管理中的相关概念 四.SpringSecurity 入门案例 前言:通常我们写http接口是不会用 ...

  8. springsecurity sessionregistry session共享_要学就学透彻!Spring Security 中 CSRF 防御源码解析...

    今日干货 刚刚发表查看:66666回复:666 公众号后台回复 ssm,免费获取松哥纯手敲的 SSM 框架学习干货. 上篇文章松哥和大家聊了什么是 CSRF 攻击,以及 CSRF 攻击要如何防御.主要 ...

  9. 权限操作-springSecurity快速入门-使用自定义页面

    使用自定义页面 spring-security.xml配置 <?xml version="1.0" encoding="UTF-8"?> <b ...

最新文章

  1. 选redis还是memcache?
  2. RBF神经网络——直接看公式,本质上就是非线性变换后的线性变化(RBF神经网络的思想是将低维空间非线性不可分问题转换成高维空间线性可分问题)...
  3. 代码审计_strcmp比较字符串
  4. 为什么基于数字的技术公司进行机器人研究
  5. js优化工具:ECMAScript Cruncher
  6. Matplotlib 中文用户指南 4.3 文本属性及布局
  7. Network-based Fraud Detection for Social Security Fraud
  8. 本机与服务器、镜像机之间文件互传
  9. Java高级进阶学习资料!Java虚拟机的垃圾回收机制
  10. NYOJ 1272:表达式求值(2016河南省ACM-A)
  11. C#调用java类、jar包方法
  12. 序列标注模型结果评估模块seqeval学习使用
  13. 可能最详细的教程,新手如何获取Zcash钱包(ZEC)官方地址的方法
  14. 1、曾经风光无限的jsp,为什么现在很少有人使用了?
  15. 360文件管理器android,360文件管理器
  16. 计算机一级误差怎么计算,(excel最大偏差公式)偏离值怎么计算
  17. 软著申请需要多少钱?大概需要多久?
  18. Xshell 基础命令及其打包静态库和动态库命令
  19. reduce()实现数组去重
  20. 2022-2027年中国科技地产行业发展监测及投资战略研究报告

热门文章

  1. html检测用户在线离线,HTML5 --- navigator.onLine 离线检测(示例代码)
  2. 5W2H分析法,哪哪儿都能用到的方法,人生也可以套路进来
  3. 服务器不能安装exe文件,云服务器安装exe文件
  4. 人均阅读18本,揭露2022全国职场人阅读报告
  5. 从 Windows 切换到 Mac,这些不能错过的 Tips
  6. 像玩乐高一样玩 simpletun
  7. 热点 | Excel不“香”了,数据分析首选Python!
  8. 甲骨文重磅发布:客户现可将自治数据库部署在自己的数据中心
  9. 新生代的他们,正在续写“黑客”传奇
  10. 钉钉辟谣“老师能打开学生摄像头”;HTC 关闭官方社区;​Node.js 安全版本发布 | 极客头条...