1. SpringSecurity简介
![截屏2021-12-24 20.29.01](https://gitee.com/xz_zelda/images/raw/master/uPic/%E6%88%AA%E5%B1%8F2021-12-24%2020.29.01.png)
1.1 相关概念
Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。
关于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。
-
用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登陆。
-
用户授权:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户说具有的权限是不同的。比如对一个文件夹来说,有的用户只能进行读取,有的用户可以进行修改。一般来说,系统不会为不同的用户分配不同的角色,二每个角色则对应一些列的权限。通俗点说就是系统判断用户是否有权限去做某些事情。
1.2 特点
-
与Spring无缝整合
-
全面的权限控制
-
专门为web开发而设计
旧版本不能脱离Web环境使用
新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入核心模块就可以脱离Web环境
-
重量级
2. 基于注解的开发
- 配置类注入bean
注意此时和xml配置文件的不同
//表示当前类是配置类,作用大致相当于以前的Spring-context.xml文件
@Configuration
public class MyAnnotationConfiguration {
//@Bean注解相当于做了下面的xml标签的
//<bean id="emp" class="con.xz.entity.Employee">
@Bean
public Employee getEmployee() {
return new Employee();
}
}
- 读取配置文件并测试
AnnotationConfigApplicationContext是此时的IOC容器
public class SpringTest {
public static void main(String[] args) {
//以前使用 new ClassPathXmlApplicationContext("")
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyAnnotationConfiguration.class);
Employee bean = applicationContext.getBean(Employee.class);
System.out.println(bean);
applicationContext.close();
}
}
3. HelloWorld
3.1 SpringMVC准备工作
3.1.1 pom文件
- 打包方式war
<packaging>war</packaging>
- 加入SpringMVC环境需要的依赖
<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>
- maven打包时报错,提示找不到web.xml文件,下面是解决办法,在pom.xml中加入:
<build>
<finalName>web-app-name</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
<configuration>
<webXml>web\WEB-INF\web.xml</webXml>
</configuration>
</plugin>
</plugins>
</build>
3.1.2 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.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan
base-package="com.xz"></context:component-scan>
<bean
class="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>
3.1.3 web.xml配置
配置DispatcherServlet
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 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>
3.1.4 导入例子文件
- 后端Java代码
- 前端jsp代码
3.2 加入SpringSecurity
- SpringSecurity依赖
<!-- 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>
- 在web.xml中加入filter过滤器
SpringSecurity 使用的是过滤器 Filter 而不是拦截器 Interceptor,意味着 SpringSecurity
能够管理的不仅仅是 SpringMVC 中的 handler 请求,还包含 Web 应用中所有请求。比如: 项目中的静态资源也会被拦截,从而进行权限控制。
<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>
特 别 注 意 : springSecurityFilterChain 标 签 中 必 须 是 springSecurityFilterChain。因为 springSecurityFilterChain 在 IOC 容器中对应真正执行权限 控制的二十几个 Filter,只有叫这个名字才能够加载到这些 Filter。
- 加入配置类
//注意这个类一定要放在自动扫描的包下,不然所有的配置都不会生效
//@Configuration//将当前类标记为配置类
@EnableWebSecurity//启用web环境下权限控制的功能
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
}
- 显示效果
所有请求都被SpringSecurity拦截,要求登录才可以访问。
静态资源也都被拦截,要求登录。
登录失败有错误提示。
4. SpringSecurity操作实验
4.1 放行首页和静态资源
放行首页和静态资源
在配置类中重写父类的 configure(HttpSecurity security)方法。
原方法:
protected void configure(HttpSecurity http) throws Exception {
this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http
.authorizeRequests().anyRequest())//所有请求都需要认证
.authenticated()
.and())
.formLogin().and())//跳转到表单页面
.httpBasic();
}
重写后
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);注释掉将取消父类方法中的默认规则
//对请求进行授权
http.authorizeRequests()
//使用 ANT 风格设置要授权的 URL
.antMatchers("/layui/**","/index.jsp")
//允许上面使用 ANT 风格设置的全部请求
.permitAll()
.and()
.authorizeRequests()
//其他未设置的全部请求
.anyRequest()
//需要认证
.authenticated();
}
总结:
第一个是表示需要认真请求。
第二个表示匹配的路径。
第三个表示该路径通行的条件。
如果还有其他需要设置的,加个and()然后再三个步骤走起
效果:未认证的请求会跳转到 403 错误页面。
4.2 未认证请求跳转到登录页
未认证请求跳转到登录页
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.......
.and()
//设置未授权请求跳转到登录页面
.formLogin()
//指定登录页
.loginPage("/index.jsp")
// 指定提交登录表单的地址,覆盖默认地址
.loginPage("/do/login.html")
;
}
指定登录页前后 SpringSecurity 登录地址变化:
指定前:
/login GET - the login form
/login POST - process the credentials and if valid authenticate the user
/login?error GET - redirect here for failed authentication attempts
/login?logout GET - redirect here after successfully logging out
指定后
/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
4.3 设置登录系统的账号密码
设置登录系统的账号、密码
4.3.1 页面设置
- index.jsp页面设置表单
<form action="${pageContext.request.contextPath }/do/login.html" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
_csrf:Cross-site request forgery 跨站请求伪造
/do/login.html:是指定提交登录表单的地址,覆盖loginPage()方法中设置的默认值/index.jsp POST
- 账号密码请求参数名
SpringSecurity 默认账号的请求参数名:username
SpringSecurity 默认密码的请求参数名:password
.usernameParameter("loginAcct") // 定制登录账号的请求参数名 .passwordParameter("userPswd") // 定制登录密码的请求参数名
4.3.2 后端代码
- 设置登录成功后默认前往的页面、定制登录账号密码的请求参数名
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.....
// loginProcessingUrl()方法指定了登录地址,就会覆盖loginPage()方法中设置的默认值/index.jsp POST
.loginProcessingUrl("/do/login.html")
// 定制登录账号的请求参数名
.usernameParameter("loginAcct")
// 定制登录密码的请求参数名
.passwordParameter("userPswd")
// 登录成功后前往的地址
.defaultSuccessUrl("/main.html")
;
}
- 重写另外一个父类的方法,来设置登录系统的账号密码
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth);
auth.inMemoryAuthentication()
.withUser("admin")
.password("123123")
.roles("ADMIN")
.and()
.withUser("Zelda")
.password("Zelda")
.authorities("SAVE","EDIT")
;
}
4.4 退出登录
4.4.1 禁用csrf
如果 CSRF 功能没有禁用,那么退出请求必须是 POST 方式。如果禁用了 CSRF 功能则任何请求方式都可以。
- 后端
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
......
//禁用csrf
.csrf()
.disable()
//开启退出功能
.logout()
//指定退出请求的URL地址
.logoutUrl("/do/logout.html")
//退出成功后前往的地址
.logoutSuccessUrl("/index.jsp")
;
}
- 前端
<a id="logoutAnchor" href="${pageContext.request.contextPath}/do/logout.html">退出</a>
4.4.2 启用csrf
- 前端利用post表单提交,给超链接的DOM对象绑定单击响应函数
<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>
- 后端:基本同上,取消禁用csrf
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
......
//禁用csrf
//.csrf()
//.disable()
//开启退出功能
.logout()
//指定退出请求的URL地址
.logoutUrl("/do/logout.html")
//退出成功后前往的地址
.logoutSuccessUrl("/index.jsp")
;
}
4.5 角色权限的访问控制
- 给不同的user设定不同的角色roles和权限authorities
.roles(“ADMIN”,“学徒”):设定admin角色为“ADMIN”和“大师”
.authorities(“外门弟子”):设定admin权限为"外门弟子")
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("123123")
.roles("ADMIN","学徒")
.authorities("外门弟子")
.and()
.withUser("Zelda")
.password("Zelda")
.authorities("SAVE", "EDIT")
.roles("大师")
;
}
- 对不同的页面的权限进行设置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/layui/**", "/index.jsp")
.permitAll()
.antMatchers("/level1/**")
.hasRole("大师")
.antMatchers("/level2/**")
.hasAuthority("外门弟子")
}
对"/level1/**"文件下的读取权限只有role为“大师”才可以访问
注意:SpringSecurity 会在角色字符串前面加“ROLE_”前缀
之所以要强调这个事情,是因为将来从数据库查询得到的用户信息、角色信息、权限信息需要我们自己手动组装。手动组装时需要我们自己给角色字符串前面加“ROLE_” 前缀。
4.6 自定义403错误页面
403错误页面用no_auth.jsp代替,主要代码如下:
<!-- 内容主体区域 -->
<div style="padding: 15px;">
<h1>非常抱歉!您没有访问这个功能的权限!(回家照照镜子)</h1>
<h2>${message }</h2>
</div>
4.6.1 方式一
- configure类
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
......
.and()
.exceptionHandling()
.accessDeniedPage("/to/no/auth/page.html")
}
- controller层
@RequestMapping("/to/no/auth/page.html")
public String toNoAuthPage() {
return "no_auth";
}
4.6.2 方式二
configure类:利用原生的servlet进行页面跳转
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
......
.exceptionHandling()
.accessDeniedHandler(new AccessDeniedHandler() {
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletRequest.setAttribute("message",e.getMessage());
httpServletRequest.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(httpServletRequest,httpServletResponse);
}
})
;
}
4.7 remember-me
4.7.1 内存版
configure类:HttpSecurity 对象调用 rememberMe()方法。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
......
//开启remember-me功能
.rememberMe()
//定制remember-me名称
//.rememberMeParameter("rememberMeParam")
;
}
登录表单携带名为 remember-me 的请求参数。具体做法是将登录表单中的checkbox 的 name 设置为 remember-me
<div class="layui-form-item">
<input type="checkbox" name="remember-me"
title="记住我"> <a href="forget.html">忘记密码?</a>
</div>
如果不能使用“ remember-me ”作为请求参数名称,可以使用**rememberMeParameter()**方法定制。
原理:
通过开发者工具看到浏览器端存储了名为 remember-me 的 Cookie。根据这个 Cookie 的 value 在服务器端找到以前登录的 User。而且这个 Cookie 被设置为存储 2 个星期的时间。
4.7.2 数据库版
为了让服务器重启也不影响记住登录状态,换浏览器访问时,也可以记住用户名和密码,将用户登录状态信息存入数据库。
- 连接数据库
加入数据库相关依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
配置数据源,这里只用了Spring的jdbcTemplate作为演示
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"></property>
<property name="password" value="123123123"></property>
<property name="url"
value="jdbc:mysql://localhost:3306/security?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&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>
创建数据库
CREATE DATABASE `security` CHARACTER SET utf8;
- 在 WebAppSecurityConfig 类中注入数据源
@Autowired
private DataSource dataSource;
- 启用令牌仓库功能
configure类:
- 创建JdbcTokenRepositoryImpl对象,并加入数据源
- http.authorizeRequests()中开启remember-me功能,并加入JdbcTokenRepositoryImpl对象
@Override
protected void configure(HttpSecurity http) throws Exception {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
http.authorizeRequests()
.......
//开启remember-me功能
.rememberMe()
//定制remember-me名称
//.rememberMeParameter("rememberMeParam")
.tokenRepository(jdbcTokenRepository)
;
}
注意:需要进入 JdbcTokenRepositoryImpl 类中找到创建 persistent_logins 表的 SQL 语句创建
这里的表名和表内容必须如下:
CREATE TABLE persistent_logins (
username VARCHAR (64) NOT NULL,
series VARCHAR (64) PRIMARY KEY,
token VARCHAR (64) NOT NULL,
last_used TIMESTAMP NOT NULL
);
在登陆后会将用户储存在这个表中,进行持久化操作。此后在其他浏览器或重启服务器后就会remember-me
4.8 查询数据库完成认证
- 实现接口UserDetailsService的loadUserByUsername方法
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private JdbcTemplate jdbcTemplate;
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//1.使用 SQL 语句根据用户名查询用户对象
String sql ="SELECT id,loginacct,userpswd,username,email FROM t_admin WHERE loginacct=?;";
Map<String, Object> resultMap = jdbcTemplate.queryForMap(sql, s);
String loginacct = resultMap.get("loginacct").toString();
String userpswd = resultMap.get("userpswd").toString();
//2.给Admin设置角色权限信息:方式一
List<GrantedAuthority> list = AuthorityUtils.createAuthorityList("ADMIN", "ROLE_学徒");
// 2.给Admin设置角色权限信息:方式二
// List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
// authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
// authorities.add(new SimpleGrantedAuthority("UPDATE"));
// 3.把admin对象和authorities封装到UserDetails中
User user = new User(loginacct, userpswd, list);
return user;
}
}
这里分成三个步骤:查询用户和密码、用list设定权限信息、将把admin对象和authorities封装到UserDetails中并返回
- 使用自定义UserDetailsService完成登录
@Autowired
private MyUserDetailsService myUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
“ROLE_”前缀问题
在自定义的UserDetailsService 中 , 使org.springframework.security.core.authority.AuthorityUtils.createAuthorityList(String…) 工具方法获取创建 SimpleGrantedAuthority 对象添加角色时需要手动在角色名称前 加“ROLE_”前缀。
4.9 应用自定义密码加密规则
4.9.1 MD5加密
- 自定义类实现 org.springframework.security.crypto.password.PasswordEncoder(使用没有过时的)接口。
encode()方法对明文进行加密。
matches()方法对明文加密后和密文进行比较。
@Component
public class MyPasswordEncoder implements PasswordEncoder {
public String encode(CharSequence charSequence) {
return md5(charSequence.toString());
}
public boolean matches(CharSequence charSequence, String s) {
return Objects.equals(encode(charSequence),s);
}
/*
*对明文字符串进行md5加密
* */
private String md5(String source) {
// 1.判断source是否有效
if (source == null || source.length() == 0) {
// 2.如果不是有效字符串抛出异常
throw new RuntimeException();
}
try {
// 3.获取MessageDigest对象
String algorithm = "md5";
MessageDigest messageDisgest = MessageDigest.getInstance(algorithm);
// 4.获取明文字符串对应的字节数组
byte[] input = source.getBytes();
// 5.执行加密
byte[] output = messageDisgest.digest(input);
// 6.创建BigInteger对象
int signum = 1;
BigInteger bigInteger = new BigInteger(signum, output);
// 7.按照16进制将BigInteger的值转换为字符串
int radix = 16;
String encoded = bigInteger.toString(radix).toUpperCase();
return encoded;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
- 注入并调用密码加密规则
在WebAppSecurityConfig extends WebSecurityConfigurerAdapter类中
@Autowired
private MyPasswordEncoder myPasswordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(myPasswordEncoder);
}
4.9.2 带盐值的加密
SpringSecurity 提供的 BCryptPasswordEncoder 加密规则。
BCryptPasswordEncoder 创建对象后代替自定义 passwordEncoder 对象即可。 BCryptPasswordEncoder 在加密时通过加入随机盐值让每一次的加密结果都不同。能够 避免密码的明文被猜到。
而在对明文和密文进行比较时,BCryptPasswordEncoder 会在密文的固定位置取出 盐值,重新进行加密。
如下是BCryptPasswordEncoder测试:
public static void main(String[] args) {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode("123");
System.out.println(encode);
//"123"加密后的密码
//$2a$10$PQgV9JqgvSnV/lqoyl/lJ.W6kKoZ47r3RI4gCUqOy/zztsrWRvksq
//$2a$10$CYeHPjoyBQ3.KGe0RlpQPeezMRxg8T8.0iSfojrLjw2j7oRe7ueoq
//$2a$10$2IU5rcg72ntviPnscApVXubNdlwI6dwq.cJ.2pOYCSvXuO96a3MOe
//$2a$10$hFDJx7G3IiqzqpvKTxmLQug82HHWY3WdX6CO84sbylFgr2R9WapWK
boolean matches = bCryptPasswordEncoder.matches("123", "$2a$10$CYeHPjoyBQ3.KGe0RlpQPeezMRxg8T8.0iSfojrLjw2j7oRe7ueoq");
System.out.println(matches);
}
加入到SpringSecurity:
- 为了能在IOC容器中自动注入BCryptPasswordEncoder,需要Import
@Import(org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.class)
- 同4.9.1注入BCryptPasswordEncoder,并将自己写的加密方法换成bCryptPasswordEncoder
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
}