day08【后台】权限控制-上

1、密码加密

1.1、PasswordEncoder接口

  • PasswordEncoder接口的代码如下:

    • 将明文密码加密为密文密码
    • 判断明文密码是否与密文密码一致
public interface PasswordEncoder {/*** Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or* greater hash combined with an 8-byte or greater randomly generated salt.*/String encode(CharSequence rawPassword);/*** Verify the encoded password obtained from storage matches the submitted raw* password after it too is encoded. Returns true if the passwords match, false if* they do not. The stored password itself is never decoded.** @param rawPassword the raw password to encode and match* @param encodedPassword the encoded password from storage to compare with* @return true if the raw password, after encoding, matches the encoded password from* storage*/boolean matches(CharSequence rawPassword, String encodedPassword);}

1.2、自定义加密规则

  • 自定义加密规则:MyPasswordEncoder.java

@Component
public class MyPasswordEncoder implements PasswordEncoder {@Overridepublic String encode(CharSequence rawPassword) {return privateEncode(rawPassword);}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {// 1.对明文密码进行加密String formPassword = privateEncode(rawPassword);// 2.声明数据库密码String databasePassword = encodedPassword;// 3.比较return Objects.equals(formPassword, databasePassword);}private String privateEncode(CharSequence rawPassword) {try {// 1.创建MessageDigest对象String algorithm = "MD5";MessageDigest messageDigest = MessageDigest.getInstance(algorithm);// 2.获取rawPassword的字节数组byte[] input = ((String)rawPassword).getBytes();// 3.加密byte[] output = messageDigest.digest(input);// 4.转换为16进制数对应的字符String encoded = new BigInteger(1, output).toString(16).toUpperCase();return encoded;} catch (NoSuchAlgorithmException e) {e.printStackTrace();return null;}}}

1.3、注入自定义加密规则

  • WebAppSecurityConfig.java配置类中注入自定义加密规则

@Autowired
private MyPasswordEncoder myPasswordEncoder;@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(myUserDetailsService).passwordEncoder(myPasswordEncoder);}

1.4、测试

  • 将数据库密码修改为密文

  • 成功登陆~

2、带盐值的加密

2.1、概念

借用生活中烹饪时加盐值不同, 菜肴的味道不同这个现象, 在加密时每次使用一个随机生成的盐值, 让加密结果不固定

2.2、测试BCryptPasswordEncoder

  • 新建测试类SecurityTest.java,测试带盐值的加密

public class SecurityTest {public static void main(String[] args) {// 1.创建BCryptPasswordEncoder对象BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();// 2.准备明文字符串String rawPassword = "123123";// 3.加密String encode = passwordEncoder.encode(rawPassword);System.out.println(encode);// $2a$10$3YODojJmtbcOzHqB6bjZhO2CR7l9pPDfxBsnYz2voBHw5Ro.5bMAm// $2a$10$UeZXBF9bPMipZuFp0djjj.vnShI2J097JtgGHAZX5mtJ49FMiP6XK// $2a$10$ucdma3RFSGVgNc30nGxu9Oku66A4zHNbaxTgxRq4ucpbw7ZmeK.8m// $2a$10$kpGqUaCvRGA5rE0.GNyvGugHBPZPM8bKQ96KJl9vxyx/N3bif72OS// $2a$10$MSDMkdzhHGIj8o6A7RUa7Oe3Iww13jy7IQRCEG.aOJa6/uI2U/Pem}}class EncodeTest {public static void main(String[] args) {// 1.准备明文字符串String rawPassword = "123123";// 2.准备密文字符串String encodedPassword = "$2a$10$ucdma3RFSGVgNc30nGxu9Oku66A4zHNbaxTgxRq4ucpbw7ZmeK.8m";// 3.创建BCryptPasswordEncoder对象BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();// 4.比较boolean matcheResult = passwordEncoder.matches(rawPassword, encodedPassword);// 一致System.out.println(matcheResult ? "一致" : "不一致");}}

2.3、带盐值的验证

  • 重写configure方法:使用BCryptPasswordEncoder进行加密
// 每次调用这个方法时会检查IOC容器中是否有了对应的bean,如果有就不会真正执行这个函数,因为bean默认是单例的
// 也可以使用@Scope(value="")注解控制是否单例
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder() {return new BCryptPasswordEncoder();
}@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(myUserDetailsService).passwordEncoder(getBCryptPasswordEncoder());}
  • 修改Heygo的密码(注意:如果userpswd长度一定要 >= 密码长度)

  • 照样能正常访问~~~

3、项目加入SpringSecurity环境

3.1、引入依赖

3.1.1、父工程中统一管理版本

  • 在父工程parent中,添加统一的依赖版本

<properties><!-- 声明属性, 对 Spring 的版本进行统一管理 --><atguigu.spring.version>4.3.20.RELEASE</atguigu.spring.version><!-- 声明属性, 对 SpringSecurity 的版本进行统一管理 --><atguigu.spring.security.version>4.2.10.RELEASE</atguigu.spring.security.version>
</properties><dependencies><!-- 此处省略若干依赖... --><!-- SpringSecurity 对 Web 应用进行权限管理 --><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId><version>${atguigu.spring.security.version}</version></dependency><!-- SpringSecurity 配置 --><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId><version>${atguigu.spring.security.version}</version></dependency><!-- SpringSecurity 标签库 --><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-taglibs</artifactId><version>${atguigu.spring.security.version}</version></dependency>
</dependencies>

3.1.2、子工程中引入依赖

  • component工程中引入SpringSecurity的依赖

<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId>
</dependency>
<!-- SpringSecurity 配置 -->
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId>
</dependency>
<!-- SpringSecurity 标签库 -->
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-taglibs</artifactId>
</dependency>

3.2、配置DelegatingFilterProxy

  • web.xml中配置DelegatingFilterProxy

<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、创建基注解的配置类

  • component工程下创建SpringSecurity的配置类

// 表示当前类是一个配置类
@Configuration// 启用Web环境下权限控制功能
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {}

3.4、谁来扫描WebAppSecurityConfig?

  • 结论: 为了让 SpringSecurity 能够针对浏览器请求进行权限控制, 需要让SpringMVC 来扫描 WebAppSecurityConfig 类。
  • 衍生问题: DelegatingFilterProxy 初始化Filter时需要一个 bean,这就需要到 IOC 容器中去查找,至于去哪个IOC容器查找,这就要看是谁扫描了 WebAppSecurityConfig
    • 如果是 Spring 扫描了 WebAppSecurityConfig, 那么 Filter 需要的 bean 就在SpringIOC 容器。
    • 如果是 SpringMVC 扫描了 WebAppSecurityConfig, 那么 Filter 需要的 bean就在 SpringMVCIOC 容器。

3.5、当头一棒

3.5.1、找不到bean

  • 启动web应用,抛出异常:org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' available

3.5.2、分析原因

  • 明确三大组件启动顺序

    • 首先: ContextLoaderListener 初始化, 创建 SpringIOC 容器
    • 其次: DelegatingFilterProxy 初始化, 查找 IOC 容器、 查找 bean
    • 最后: DispatcherServlet 初始化, 创建 SpringMVC 的 IOC 容器
  • DelegatingFilterProxy 查找 IOC 容器,然后在 IOC 容器中查找 bean 的工作机制

3.6、看源码

3.6.1、在Spring ICO容器中查找

  • DelegatingFilterProxy类中的如下代码处打上断点,initFilterBean()方法负责初始化Filter

private volatile Filter delegate;@Override
protected void initFilterBean() throws ServletException {synchronized (this.delegateMonitor) {if (this.delegate == null) {// If no target bean name specified, use filter name.if (this.targetBeanName == null) {this.targetBeanName = getFilterName();}// Fetch Spring root application context and initialize the delegate early,// if possible. If the root application context will be started after this// filter proxy, we'll have to resort to lazy initialization.WebApplicationContext wac = findWebApplicationContext();if (wac != null) {this.delegate = initDelegate(wac);}}}
}
  • Step into进入findWebApplicationContext()方法:寻找IOC容器

    • if (this.webApplicationContext != null) {成立
    • if (attrName != null) {成立

protected WebApplicationContext findWebApplicationContext() {if (this.webApplicationContext != null) {// The user has injected a context at construction time -> use it...if (this.webApplicationContext instanceof ConfigurableApplicationContext) {ConfigurableApplicationContext cac = (ConfigurableApplicationContext) this.webApplicationContext;if (!cac.isActive()) {// The context has not yet been refreshed -> do so before returning it...cac.refresh();}}return this.webApplicationContext;}String attrName = getContextAttribute();if (attrName != null) {return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);}else {return WebApplicationContextUtils.findWebApplicationContext(getServletContext());}
}
  • Step into进入WebApplicationContextUtils.findWebApplicationContext(getServletContext())方法:寻找IOC容器

public static WebApplicationContext findWebApplicationContext(ServletContext sc) {WebApplicationContext wac = getWebApplicationContext(sc);if (wac == null) {Enumeration<String> attrNames = sc.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = attrNames.nextElement();Object attrValue = sc.getAttribute(attrName);if (attrValue instanceof WebApplicationContext) {if (wac != null) {throw new IllegalStateException("No unique WebApplicationContext found: more than one " +"DispatcherServlet registered with publishContext=true?");}wac = (WebApplicationContext) attrValue;}}}return wac;
}
  • Step into进入getWebApplicationContext(sc)方法中:从ServletContext中获取IOC容器

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";public static WebApplicationContext getWebApplicationContext(ServletContext sc) {return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
  • Step into进入getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)方法中:获取整个Web应用的父容器

    • 形参attrName = org.springframework.web.context.WebApplicationContext.ROOT
    • 其中attrRoot WebApplicationContext,即Spring IOC容器(父容器)

public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {Assert.notNull(sc, "ServletContext must not be null");Object attr = sc.getAttribute(attrName);if (attr == null) {return null;}if (attr instanceof RuntimeException) {throw (RuntimeException) attr;}if (attr instanceof Error) {throw (Error) attr;}if (attr instanceof Exception) {throw new IllegalStateException((Exception) attr);}if (!(attr instanceof WebApplicationContext)) {throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);}return (WebApplicationContext) attr;
}
  • 得到IOC容器之后,就会使用IOC容器中的Bean初始化Filter;如果找不到IOC容器,就放弃

  • Step into进入initDelegate(wac):从IOC容器中获取Filter Bean

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);if (isTargetFilterLifecycle()) {delegate.init(getFilterConfig());}return delegate;
}
  • 找不到springSecurityFilterChain这个Bean便会抛异常

public boolean filterStart() {if (getLogger().isDebugEnabled()) {getLogger().debug("Starting filters");}// Instantiate and record a FilterConfig for each defined filterboolean ok = true;synchronized (filterConfigs) {filterConfigs.clear();for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {String name = entry.getKey();if (getLogger().isDebugEnabled()) {getLogger().debug(" Starting filter '" + name + "'");}try {ApplicationFilterConfig filterConfig =new ApplicationFilterConfig(this, entry.getValue());filterConfigs.put(name, filterConfig);} catch (Throwable t) {t = ExceptionUtils.unwrapInvocationTargetException(t);ExceptionUtils.handleThrowable(t);getLogger().error(sm.getString("standardContext.filterStart", name), t);ok = false;}}}return ok;
}
  • 如果初始化Filter时,没有找到Spring IOC父容器;则在第一次请求时,如果发现Filternull,也会尝试使用IOC容器去初始化它

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)throws ServletException, IOException {// Lazily initialize the delegate if necessary.Filter delegateToUse = this.delegate;if (delegateToUse == null) {synchronized (this.delegateMonitor) {delegateToUse = this.delegate;if (delegateToUse == null) {WebApplicationContext wac = findWebApplicationContext();if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: " +"no ContextLoaderListener or DispatcherServlet registered?");}delegateToUse = initDelegate(wac);}this.delegate = delegateToUse;}}// Let the delegate perform the actual doFilter operation.invokeDelegate(delegateToUse, request, response, filterChain);
}
  • 结论:Spring IOC容器为整个Web应用的父容器,然后在Spring IOC容器中并没有springSecurityFilterChain这个Bean,所以程序便会抛异常

3.7、解决方案一: IOC容器合二为一

  • 不使用 ContextLoaderListener, 让 DispatcherServlet 加载所有 Spring 配置文件。

    • DelegatingFilterProxy 在初始化时查找 IOC 容器, 找不到, 放弃。
    • 第一次请求时再次查找。
    • 找到 SpringMVCIOC 容器。
    • 从这个 IOC 容器中找到所需要的 bean
  • 遗憾: 会破坏现有程序的结构。 原本是 ContextLoaderListenerDispatcherServlet 两个组件创建两个 IOC 容器, 现在改成只有一个。

3.8、解决方案二:改源码

3.8.1、思路

  • Web应用启动时,不让DelegatingFilterProxySpring IOC容器中去找springSecurityFilterChain

  • 第一次请求时,让DelegatingFilterProxySpringMVC IOC容器中去找springSecurityFilterChain

3.8.2、改源码

  • component工程下创建与DelegatingFilterProxy同包名、同类名的类

  • Web应用启动时不加载Spring IOC容器
@Override
protected void initFilterBean() throws ServletException {synchronized (this.delegateMonitor) {if (this.delegate == null) {// If no target bean name specified, use filter name.if (this.targetBeanName == null) {this.targetBeanName = getFilterName();}// Fetch Spring root application context and initialize the delegate early,// if possible. If the root application context will be started after this// filter proxy, we'll have to resort to lazy initialization./*WebApplicationContext wac = findWebApplicationContext();if (wac != null) {this.delegate = initDelegate(wac);}*/}}
}
  • 第一次请求时,去SpringMVC IOC容器中获取springSecurityFilterChain这个Bean

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)throws ServletException, IOException {// Lazily initialize the delegate if necessary.Filter delegateToUse = this.delegate;if (delegateToUse == null) {synchronized (this.delegateMonitor) {delegateToUse = this.delegate;if (delegateToUse == null) {// 把原来的查找IOC容器的代码注释掉// WebApplicationContext wac = findWebApplicationContext();// 按我们自己的需要重新编写// 1.获取ServletContext对象ServletContext sc = this.getServletContext();// 2.拼接SpringMVC将IOC容器存入ServletContext域的时候使用的属性名String servletName = "springDispatcherServlet";String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + servletName;// 3.根据attrName从ServletContext域中获取IOC容器对象WebApplicationContext wac = (WebApplicationContext) sc.getAttribute(attrName);if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: " +"no ContextLoaderListener or DispatcherServlet registered?");}delegateToUse = initDelegate(wac);}this.delegate = delegateToUse;}}// Let the delegate perform the actual doFilter operation.invokeDelegate(delegateToUse, request, response, filterChain);
}

3.8.3、为什么可以这样改?

  • IOC容器初始化好了之后就会被放在ServletContext

    • ContextLoaderListener中的contextInitialized(ServletContextEvent event)方法中会执行IOC容器的初始化

    @Override
    public void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());
    }
    
    • contextInitialized(ServletContextEvent event)方法中会将IOC容器放到servletContext域中

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!");}Log logger = LogFactory.getLog(ContextLoader.class);servletContext.log("Initializing Spring root WebApplicationContext");if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {// Store context in local instance variable, to guarantee that// it is available on ServletContext shutdown.if (this.context == null) {this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent ->// determine parent for root web application context, if any.ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}configureAndRefreshWebApplicationContext(cwac, servletContext);}}servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isDebugEnabled()) {logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}catch (Error err) {logger.error("Context initialization failed", err);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);throw err;}
    }
    

3.9、实验效果

  • 启动Web应用并没有抛异常,并且我们还发现了 SpringSecurity 的工作原理: 在初始化时或第一次请求时准备好过滤器链。具体任务由具体过滤器来完成。
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.www.BasicAuthenticationFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
  • 访问登录页面。又是这熟悉的页面

4、目标一:放行登录页和静态资源

4.1、重写configure方法

  • 重写WebAppSecurityConfig配置类中的configure方法

// 表示当前类是一个配置类
@Configuration// 启用Web环境下权限控制功能
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                        // 对请求进行授权.antMatchers("/admin/to/login/page.html")   // 针对登录页进行设置.permitAll()                                // 无条件访问// 放行静态资源.antMatchers("/bootstrap/**")    .permitAll()                    .antMatchers("/crowd/**")       .permitAll()                    .antMatchers("/css/**")         .permitAll()                    .antMatchers("/fonts/**")       .permitAll()                    .antMatchers("/img/**")         .permitAll()                    .antMatchers("/jquery/**")      .permitAll()                    .antMatchers("/layer/**")       .permitAll()                    .antMatchers("/script/**")      .permitAll()                    .antMatchers("/ztree/**")       .permitAll().anyRequest()                   // 其他任意请求.authenticated()               // 认证后访问;}}

4.2、实验效果

  • 又是这熟悉的首页

5、目标二:登录认证(内存验证)

5.1、修改表单

  • 修改登录页面的表单

    • form表单的提交地址:action="security/do/login.html"
    • 登录异常信息:${SPRING_SECURITY_LAST_EXCEPTION.message }

<form action="security/do/login.html" method="post" class="form-signin" role="form"><h2 class="form-signin-heading"><i class="glyphicon glyphicon-log-in"></i> 管理员登录</h2><p>${requestScope.exception.message }</p><p>${SPRING_SECURITY_LAST_EXCEPTION.message }</p><div class="form-group has-success has-feedback"><input type="text" name="loginAcct" value="Heygo" class="form-control" id="inputSuccess4"placeholder="请输入登录账号" autofocus> <spanclass="glyphicon glyphicon-user form-control-feedback"></span></div><div class="form-group has-success has-feedback"><input type="text" name="userPswd" value="123123" class="form-control" id="inputSuccess4"placeholder="请输入登录密码" style="margin-top: 10px;"> <spanclass="glyphicon glyphicon-lock form-control-feedback"></span></div><button type="submit" class="btn btn-lg btn-success btn-block">登录</button>
</form>

5.2、重写configure方法

  • 重写配置类中的configure方法

    • 指定登录页面地址
    • 指定提交登录请求的地址
    • 指定登陆成功后要去的页面
    • 指定用户名和密码的字段名称
    • 配置内存版的tom用户
// 表示当前类是一个配置类
@Configuration// 启用Web环境下权限控制功能
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                        // 对请求进行授权.antMatchers("/admin/to/login/page.html")   // 针对登录页进行设置.permitAll()                                // 无条件访问// 放行静态资源.antMatchers("/bootstrap/**")    .permitAll()                    .antMatchers("/crowd/**")       .permitAll()                    .antMatchers("/css/**")         .permitAll()                    .antMatchers("/fonts/**")       .permitAll()                    .antMatchers("/img/**")         .permitAll()                    .antMatchers("/jquery/**")      .permitAll()                    .antMatchers("/layer/**")       .permitAll()                    .antMatchers("/script/**")      .permitAll()                    .antMatchers("/ztree/**")       .permitAll().anyRequest()                   // 其他任意请求.authenticated()               // 认证后访问.and().csrf()                           // 防跨站请求伪造功能.disable()                      // 禁用.formLogin()                   // 开启表单登录的功能.loginPage("/admin/to/login/page.html")           // 指定登录页面.loginProcessingUrl("/security/do/login.html")   // 指定处理登录请求的地址.defaultSuccessUrl("/admin/to/main/page.html")  // 指定登录成功后前往的地址.usernameParameter("loginAcct")    // 账号的请求参数名称.passwordParameter("userPswd")    // 密码的请求参数名称;}@Overrideprotected void configure(AuthenticationManagerBuilder builder) throws Exception {// 临时使用内存版登录的模式测试代码builder.inMemoryAuthentication().withUser("Heygo").password("123123").roles("ADMIN");}}

5.3、干掉拦截器

  • SpringMVC配置文件中干掉Interceptor,因为Interceptor会拦截用户的请求

    • 注释如下代码

    • Interceptor代码如下
    public class LoginInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {// 1.通过request对象获取Session对象HttpSession session = request.getSession();// 2.尝试从Session域中获取Admin对象Admin admin = (Admin) session.getAttribute(CrowdConstant.ATTR_NAME_LOGIN_ADMIN);// 3.判断admin对象是否为空if (admin == null) {// 4.抛出异常throw new AccessForbiddenException(CrowdConstant.MESSAGE_ACCESS_FORBIDEN);}// 5.如果Admin对象不为null,则返回true放行return true;}}
    

5.4、实验效果

  • 又是这熟悉的控制面板

6、目标三:退出登录

6.1、修改请求地址

  • 修改顶部导航栏退出登录的请求地址

<li><a href="seucrity/do/logout.html"><iclass="glyphicon glyphicon-off"></i> 退出系统</a></li>

6.2、重写configure方法

  • 重写configure方法:

    • 开启退出登录功能
    • 制定退出登录的请求地址
    • 指定退出登录成功后。需要重定向至哪个页面
@Override
protected void configure(HttpSecurity security) throws Exception {security.authorizeRequests()                      // 对请求进行授权.antMatchers("/admin/to/login/page.html")   // 针对登录页进行设置.permitAll()                                // 无条件访问// 放行静态资源.antMatchers("/bootstrap/**")    .permitAll()                    .antMatchers("/crowd/**")       .permitAll()                    .antMatchers("/css/**")         .permitAll()                    .antMatchers("/fonts/**")       .permitAll()                    .antMatchers("/img/**")         .permitAll()                    .antMatchers("/jquery/**")      .permitAll()                    .antMatchers("/layer/**")       .permitAll()                    .antMatchers("/script/**")      .permitAll()                    .antMatchers("/ztree/**")       .permitAll().anyRequest()                   // 其他任意请求.authenticated()               // 认证后访问.and().csrf()                           // 防跨站请求伪造功能.disable()                      // 禁用.formLogin()                   // 开启表单登录的功能.loginPage("/admin/to/login/page.html")           // 指定登录页面.loginProcessingUrl("/security/do/login.html")   // 指定处理登录请求的地址.defaultSuccessUrl("/admin/to/main/page.html")  // 指定登录成功后前往的地址.usernameParameter("loginAcct")    // 账号的请求参数名称.passwordParameter("userPswd")    // 密码的请求参数名称.and().logout()                     // 开启退出登录功能.logoutUrl("/seucrity/do/logout.html")         // 指定退出登录地址.logoutSuccessUrl("/admin/to/login/page.html") // 指定退出成功以后前往的地址;}

6.3、实验效果

  • 乌拉~

7、目标四:登录认证(数据库验证)

7.1、思路

7.2、根据adminId查询管理员信息

7.2.1、Service层

  • AdminServiceImpl中实现此方法

@Override
public Admin getAdminByLoginAcct(String username) {// 查询条件AdminExample example = new AdminExample();Criteria criteria = example.createCriteria();criteria.andLoginAcctEqualTo(username);// 执行查询List<Admin> list = adminMapper.selectByExample(example);Admin admin = list.get(0);//返回Admin对象return admin;
}

7.2.2、Mapper层

  • Mapper层代码由Mybatis逆向生成

7.3、根据adminId查询管理员所拥有的角色

7.3.1、Service层

  • 这个方法在之前就已经写好啦~

@Override
public List<Role> getAssignedRole(Integer adminId) {return roleMapper.selectAssignedRole(adminId);
}

7.3.2、Mapper层

  • 来看看之前写的SQL语句(子查询)

<select id="selectAssignedRole" resultMap="BaseResultMap">select id,name from t_role where id in (select role_id from inner_admin_role where admin_id=#{adminId})
</select>

7.4、根据adminId查询管理员所拥有的权限名

7.4.1、Service层

  • 根据adminId的值查询该Admin所拥有的权限

@Override
public List<String> getAssignedAuthNameByAdminId(Integer adminId) {return authMapper.selectAssignedAuthNameByAdminId(adminId);
}

7.4.2、Mapper层

  • 接口方法声明

List<String> selectAssignedAuthNameByAdminId(Integer adminId);
  • 编写SQL语句

    • t_auth.namet_auth表中

    • t_auth表与inner_role_auth表左外连接,得到权限与角色的对应关系

    • inner_role_authinner_admin_role表左外连接,得到角色管理员的对应关系

    • 最后取出指定adminId的权限名称

<select id="selectAssignedAuthNameByAdminId" resultType="string">SELECT DISTINCT t_auth.name FROM t_auth LEFT JOIN inner_role_auth ON t_auth.id=inner_role_auth.auth_idLEFT JOIN inner_admin_role ON inner_admin_role.role_id=inner_role_auth.role_idWHERE inner_admin_role.admin_id=#{adminId} and t_auth.name != "" and t_auth.name is not null
</select>
  • SQL左外连接分析

7.5、自定义用户类

  • 创建SecurityAdmin类,继承自User类,用于封装Admin的信息

/*** 考虑到User对象中仅仅包含账号和密码,为了能够获取到原始的Admin对象,专门创建这个类对User类进行扩展* @author Lenovo**/
public class SecurityAdmin extends User {private static final long serialVersionUID = 1L;// 原始的Admin对象,包含Admin对象的全部属性private Admin originalAdmin;public SecurityAdmin(// 传入原始的Admin对象Admin originalAdmin, // 创建角色、权限信息的集合List<GrantedAuthority> authorities) {// 调用父类构造器super(originalAdmin.getLoginAcct(), originalAdmin.getUserPswd(), authorities);// 给本类的this.originalAdmin赋值this.originalAdmin = originalAdmin;}// 对外提供的获取原始Admin对象的getXxx()方法public Admin getOriginalAdmin() {return originalAdmin;}}

7.6、实现UserDetailsService接口

  • component工程下创建UserDetailsService接口的实现类:CrowdUserDetailsService

    • 装配角色信息(注意添加"ROLE_"前缀)
    • 装配权限信息
    • 封装成securityAdmin对象,并返回此对象

@Component
public class CrowdUserDetailsService implements UserDetailsService {@Autowiredprivate AdminService adminService;@Autowiredprivate RoleService roleService;@Autowiredprivate AuthService authService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1.根据账号名称查询Admin对象Admin admin = adminService.getAdminByLoginAcct(username);// 2.获取adminIdInteger adminId = admin.getId();// 3.根据adminId查询角色信息List<Role> assignedRoleList = roleService.getAssignedRole(adminId);// 4.根据adminId查询权限信息List<String> authNameList = authService.getAssignedAuthNameByAdminId(adminId);// 5.创建集合对象用来存储GrantedAuthorityList<GrantedAuthority> authorities = new ArrayList<>();// 6.遍历assignedRoleList存入角色信息for (Role role : assignedRoleList) {// 注意:不要忘了加前缀!String roleName = "ROLE_" + role.getName();SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(roleName);authorities.add(simpleGrantedAuthority);}// 7.遍历authNameList存入权限信息for (String authName : authNameList) {SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authName);authorities.add(simpleGrantedAuthority);}// 8.封装SecurityAdmin对象SecurityAdmin securityAdmin = new SecurityAdmin(admin, authorities);return securityAdmin;}}

7.7、注入UserDetailsService

  • 在配置类中重写configure方法,使用自定义的登录逻辑

@Autowired
private UserDetailsService userDetailsService;@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {// 临时使用内存版登录的模式测试代码/*builder.inMemoryAuthentication().withUser("Heygo").password("123123").roles("ADMIN");*/builder.userDetailsService(userDetailsService);}

7.8、实验测试

  • 由于还没有实现加密功能,我们先将数据库密码改为明文密码

  • Oneby登陆成功

day08【后台】权限控制-上相关推荐

  1. Java系列技术之SSH整合+用户权限控制模型项目-钟洪发-专题视频课程

    Java系列技术之SSH整合+用户权限控制模型项目-62人已学习 课程介绍         将前面讲过的Spring5.SpringMVC.Hibernate5和前端框架Easyui整合起来最终完成一 ...

  2. 方法级别权限控制-基本介绍与JSR250注解使用

    服务器端方法级权限控制 在服务器端我们可以通过Spring security提供的注解对方法来进行权限控制.Spring Security在方法的权限控制上支持三种类型的注解,JSR-250注解.@S ...

  3. yii2 后台权限验证获取用户身份_前、后端分离权限控制设计与实现

    作者:佚名 来源:Web开发 简述 近几年随着react.angular.vue等前端框架兴起,前后端分离的架构迅速流行.但同时权限控制也带来了问题. 网上很多前.后端分离权限仅仅都仅仅在描述前端权限 ...

  4. tp3.2.3权限控制二之后台管理部分,及菜单栏目显示问题

    上篇记录了一下权限控制部分的实现过程,以及实现的代码.但那些只是实现了简单的权限控制,具体的后台当然要有用户的管理,用户组的管理,规则管理等.这篇主要是写后台部分,和上篇凑成一个完整的环. 第一篇文章 ...

  5. 3YAdmin-专注通用权限控制与表单的后台管理系统模板

    3YAdmin基于React+Antd构建.GitHub搜索React+Antd+Admin出来的结果没有上百也有几十个,为什么还要写这个东西呢? 一个后台管理系统的核心我认为应该是权限控制,表单以及 ...

  6. 基于Vue实现后台系统权限控制

    原文地址:http://refined-x.com/2017/08/29/基于Vue实现后台系统权限控制/,转载请注明出处. 用Vue这类双向绑定框架做后台系统再适合不过,后台系统相比普通前端项目除了 ...

  7. day09【后台】权限控制-下

    day09[后台]权限控制-下 1.目标5:密码加密 1.1.修改数据库表结构 由于之前使用MD5加密,密码字段长度为32位:现采用带盐值的加密,需要修改密码字段的长度 顺带把密码也改了 1.2.注入 ...

  8. yii2搭建完美后台并实现rbac权限控制实例教程

    作者:白狼 出处:http://www.manks.top/yii2_fra... 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权 ...

  9. 【JAVA EE#6】【传智书城·源码阅读】后台管理模块:权限控制+页面分析+商品管理+销售榜单+订单管理+公告管理+项目结构思维导图

    权限控制 普通用户只能访问client文件夹下面的jsp文件,对于没有权限操作的admin文件夹就会提示错误,而超级用户同时可以访问两者,一直很好奇这个权限限制怎么实现的. 原来在存在一个AdminP ...

最新文章

  1. cocos2d-x 3.0 Loading界面实现
  2. c语言中 printf(quot;nquot;),关于C语言 printf(quot;%d\nquot;,printf(quot;%dquot;,printf(quot;%dquot;...
  3. 正方形与圆的爱恨纠缠...
  4. codevs 1283 等差子序列
  5. Android ListView 实现下拉刷新上拉加载
  6. mac install wget
  7. python基础代码大全-Python-基础汇总
  8. 邹博机器学习代码分析(1)-线性回归
  9. 王文京:纵横30年,阵阵桂花香
  10. 数学模型学习——图与网络
  11. 经典编程题目-古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子
  12. 中文拼音转换成CMU的音素工具
  13. 抖音短视频运营中的六大定位法
  14. Caution: request is not finished yet
  15. linux pdf 笔记软件,可以编辑在PDF上做笔记的笔记软件:Xournal
  16. 对于Linux基础网络设置
  17. 利用tushare pro根据财务指标选股
  18. 数据结构与算法是什么?
  19. 生活随记 - 关于一万六/月的房子出租
  20. Golang中的strings.Builder

热门文章

  1. 车座自动排水阀行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  2. 计算机中的基础元素,数据结构基础
  3. 教你打通 Git 任督二脉
  4. 到底什么是“云网融合”?
  5. 5 万人在家办公,如何高效协同?字节跳动提供了一份指南
  6. 详解 TypyScript 的一个怪异行为
  7. 一文详解 SpringBoot 多数据源中的分布式事务
  8. 做消息推送 8 年的极光,为何做物联网 JIoT 平台?
  9. 全栈开发者意味着什么?
  10. 2019 下半年,程序员怎么过?