day08【后台】权限控制-上
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
就在Spring
的IOC
容器。 - 如果是
SpringMVC
扫描了WebAppSecurityConfig
, 那么Filter
需要的bean
就在SpringMVC
的IOC
容器。
- 如果是
3.5、当头一棒
3.5.1、找不到bean
- 启动
web
应用,抛出异常:org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' available
3.5.2、分析原因
- 明确三大组件启动顺序
- 首先:
ContextLoaderListener
初始化, 创建Spring
的IOC
容器 - 其次:
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
- 其中
attr
为Root 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
父容器;则在第一次请求时,如果发现Filter
为null
,也会尝试使用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
容器, 找不到, 放弃。- 第一次请求时再次查找。
- 找到
SpringMVC
的IOC
容器。 - 从这个
IOC
容器中找到所需要的bean
。
- 遗憾: 会破坏现有程序的结构。 原本是
ContextLoaderListener
和DispatcherServlet
两个组件创建两个IOC
容器, 现在改成只有一个。
3.8、解决方案二:改源码
3.8.1、思路
Web
应用启动时,不让DelegatingFilterProxy
在Spring IOC
容器中去找springSecurityFilterChain
第一次请求时,让
DelegatingFilterProxy
在SpringMVC 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.name
在t_auth
表中
- 将
t_auth
表与inner_role_auth
表左外连接,得到权限与角色的对应关系
- 将
inner_role_auth
与inner_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【后台】权限控制-上相关推荐
- Java系列技术之SSH整合+用户权限控制模型项目-钟洪发-专题视频课程
Java系列技术之SSH整合+用户权限控制模型项目-62人已学习 课程介绍 将前面讲过的Spring5.SpringMVC.Hibernate5和前端框架Easyui整合起来最终完成一 ...
- 方法级别权限控制-基本介绍与JSR250注解使用
服务器端方法级权限控制 在服务器端我们可以通过Spring security提供的注解对方法来进行权限控制.Spring Security在方法的权限控制上支持三种类型的注解,JSR-250注解.@S ...
- yii2 后台权限验证获取用户身份_前、后端分离权限控制设计与实现
作者:佚名 来源:Web开发 简述 近几年随着react.angular.vue等前端框架兴起,前后端分离的架构迅速流行.但同时权限控制也带来了问题. 网上很多前.后端分离权限仅仅都仅仅在描述前端权限 ...
- tp3.2.3权限控制二之后台管理部分,及菜单栏目显示问题
上篇记录了一下权限控制部分的实现过程,以及实现的代码.但那些只是实现了简单的权限控制,具体的后台当然要有用户的管理,用户组的管理,规则管理等.这篇主要是写后台部分,和上篇凑成一个完整的环. 第一篇文章 ...
- 3YAdmin-专注通用权限控制与表单的后台管理系统模板
3YAdmin基于React+Antd构建.GitHub搜索React+Antd+Admin出来的结果没有上百也有几十个,为什么还要写这个东西呢? 一个后台管理系统的核心我认为应该是权限控制,表单以及 ...
- 基于Vue实现后台系统权限控制
原文地址:http://refined-x.com/2017/08/29/基于Vue实现后台系统权限控制/,转载请注明出处. 用Vue这类双向绑定框架做后台系统再适合不过,后台系统相比普通前端项目除了 ...
- day09【后台】权限控制-下
day09[后台]权限控制-下 1.目标5:密码加密 1.1.修改数据库表结构 由于之前使用MD5加密,密码字段长度为32位:现采用带盐值的加密,需要修改密码字段的长度 顺带把密码也改了 1.2.注入 ...
- yii2搭建完美后台并实现rbac权限控制实例教程
作者:白狼 出处:http://www.manks.top/yii2_fra... 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权 ...
- 【JAVA EE#6】【传智书城·源码阅读】后台管理模块:权限控制+页面分析+商品管理+销售榜单+订单管理+公告管理+项目结构思维导图
权限控制 普通用户只能访问client文件夹下面的jsp文件,对于没有权限操作的admin文件夹就会提示错误,而超级用户同时可以访问两者,一直很好奇这个权限限制怎么实现的. 原来在存在一个AdminP ...
最新文章
- cocos2d-x 3.0 Loading界面实现
- c语言中 printf(quot;nquot;),关于C语言 printf(quot;%d\nquot;,printf(quot;%dquot;,printf(quot;%dquot;...
- 正方形与圆的爱恨纠缠...
- codevs 1283 等差子序列
- Android ListView 实现下拉刷新上拉加载
- mac install wget
- python基础代码大全-Python-基础汇总
- 邹博机器学习代码分析(1)-线性回归
- 王文京:纵横30年,阵阵桂花香
- 数学模型学习——图与网络
- 经典编程题目-古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子
- 中文拼音转换成CMU的音素工具
- 抖音短视频运营中的六大定位法
- Caution: request is not finished yet
- linux pdf 笔记软件,可以编辑在PDF上做笔记的笔记软件:Xournal
- 对于Linux基础网络设置
- 利用tushare pro根据财务指标选股
- 数据结构与算法是什么?
- 生活随记 - 关于一万六/月的房子出租
- Golang中的strings.Builder